From f8d9dd52d340ee73ef39d47080c3511ddbf89a94 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 23 Aug 2024 09:29:10 +1000 Subject: [PATCH 1/5] translations --- lang/en/texts.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lang/en/texts.php b/lang/en/texts.php index fa77340579ce..55ded895bd16 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5319,6 +5319,8 @@ $lang = array( 'one_page_checkout' => 'One-Page Checkout', 'one_page_checkout_help' => 'Enable the new single page payment flow', 'applies_to' => 'Applies To', + 'accept_purchase_order' => 'Accept Purchase Order', + 'round_to_seconds' => 'Round To Seconds', ); return $lang; From ac742cc893ad3d58cc9e0ef4dd1d41f4c8889b4d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 23 Aug 2024 09:29:36 +1000 Subject: [PATCH 2/5] Updated translations --- lang/nl/texts.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lang/nl/texts.php b/lang/nl/texts.php index 1880ab664fa8..0745fcf96c1f 100644 --- a/lang/nl/texts.php +++ b/lang/nl/texts.php @@ -4847,7 +4847,7 @@ E-mail: :email
', 'failed' => 'Mislukt', 'client_contacts' => 'Klantencontacten', 'sync_from' => 'Synchroniseren van', - 'gateway_payment_text' => 'Facturen: :invoices voor :amount voor opdrachtgever :client', + 'gateway_payment_text' => 'Factuur/facturen: :invoices voor :amount voor opdrachtgever :client', 'gateway_payment_text_no_invoice' => 'Betaling zonder factuur voor bedrag :amount voor opdrachtgever :client', 'click_to_variables' => 'Klik hier om alle variabelen te zien.', 'ship_to' => 'Verzend naar', @@ -5314,6 +5314,11 @@ E-mail: :email
', 'comments_only' => 'Alleen opmerkingen', 'payment_balance_on_file' => 'Huidig saldo', 'ubl_email_attachment_help' => 'Voor meer e-factuurinstellingen, navigeer naar :here', + 'stop_task_to_add_task_entry' => 'U moet de taak stoppen alvorens een nieuw item toe te voegen.', + 'xml_file' => 'XML-bestand', + 'one_page_checkout' => 'Afrekenen op één pagina', + 'one_page_checkout_help' => 'Schakel de nieuwe betaalflow \'afrekenen op één pagina\' in', + 'applies_to' => 'Is van toepassing op', ); return $lang; From b24d8431648129ee7081d8ff6d9d5b25725a6d3f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 23 Aug 2024 14:17:37 +1000 Subject: [PATCH 3/5] Updates for QB --- .../ImportQuickbooksController.php | 64 +++-- .../Import/Quickbooks/CustomerRepository.php | 11 - .../Import/Quickbooks/InvoiceRepository.php | 10 - .../Import/Quickbooks/ItemRepository.php | 11 - .../Import/Quickbooks/PaymentRepository.php | 10 - .../Import/Quickbooks/Repository.php | 38 --- app/Services/Import/Quickbooks/Auth.php | 64 ----- .../Quickbooks/Contracts/SdkInterface.php | 14 -- .../Import/Quickbooks/QuickbooksService.php | 45 ++-- .../Repositories/CompanyTokensRepository.php | 78 ------- app/Services/Import/Quickbooks/SdkWrapper.php | 218 ++++++++++++------ app/Services/Import/Quickbooks/Service.php | 89 ------- routes/web.php | 1 - 13 files changed, 199 insertions(+), 454 deletions(-) delete mode 100644 app/Repositories/Import/Quickbooks/CustomerRepository.php delete mode 100644 app/Repositories/Import/Quickbooks/InvoiceRepository.php delete mode 100644 app/Repositories/Import/Quickbooks/ItemRepository.php delete mode 100644 app/Repositories/Import/Quickbooks/PaymentRepository.php delete mode 100644 app/Repositories/Import/Quickbooks/Repository.php delete mode 100644 app/Services/Import/Quickbooks/Auth.php delete mode 100644 app/Services/Import/Quickbooks/Contracts/SdkInterface.php delete mode 100644 app/Services/Import/Quickbooks/Repositories/CompanyTokensRepository.php delete mode 100644 app/Services/Import/Quickbooks/Service.php diff --git a/app/Http/Controllers/ImportQuickbooksController.php b/app/Http/Controllers/ImportQuickbooksController.php index 8f93651a7a25..9030d3c33a01 100644 --- a/app/Http/Controllers/ImportQuickbooksController.php +++ b/app/Http/Controllers/ImportQuickbooksController.php @@ -3,28 +3,19 @@ namespace App\Http\Controllers; use App\Http\Requests\Quickbooks\AuthorizedQuickbooksRequest; -use Closure; -use App\Utils\Ninja; -use App\Models\Company; use App\Libraries\MultiDB; -use Illuminate\Support\Str; -use Illuminate\Http\Request; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; -use App\Jobs\Import\QuickbooksIngest; use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\Validator; use App\Http\Requests\Quickbooks\AuthQuickbooksRequest; use App\Services\Import\Quickbooks\QuickbooksService; class ImportQuickbooksController extends BaseController { - private array $import_entities = [ - 'client' => 'Customer', - 'invoice' => 'Invoice', - 'product' => 'Item', - 'payment' => 'Payment' - ]; + // private array $import_entities = [ + // 'client' => 'Customer', + // 'invoice' => 'Invoice', + // 'product' => 'Item', + // 'payment' => 'Payment' + // ]; public function onAuthorized(AuthorizedQuickbooksRequest $request) { @@ -34,12 +25,11 @@ class ImportQuickbooksController extends BaseController $qb = new QuickbooksService($company); $realm = $request->query('realmId'); - $access_token_object = $qb->getAuth()->accessToken($request->query('code'), $realm); - nlog($access_token_object); //OAuth2AccessToken - $company->quickbooks = $access_token_object; - $company->save(); + $access_token_object = $qb->getAuth()->accessTokenFromCode($request->query('code'), $realm); + $qb->getAuth()->saveOAuthToken($access_token_object); + + return redirect(config('ninja.react_url')); - return response()->json(['message' => 'Success'], 200); //todo swapout for redirect to UI } /** @@ -63,29 +53,29 @@ class ImportQuickbooksController extends BaseController public function preimport(string $type, string $hash) { - // Check for authorization otherwise - // Create a reference - $data = [ - 'hash' => $hash, - 'type' => $type - ]; - $this->getData($data); + // // Check for authorization otherwise + // // Create a reference + // $data = [ + // 'hash' => $hash, + // 'type' => $type + // ]; + // $this->getData($data); } protected function getData($data) { - $entity = $this->import_entities[$data['type']]; - $cache_name = "{$data['hash']}-{$data['type']}"; - // TODO: Get or put cache or DB? - if(! Cache::has($cache_name)) { - $contents = call_user_func([$this->service, "fetch{$entity}s"]); - if($contents->isEmpty()) { - return; - } + // $entity = $this->import_entities[$data['type']]; + // $cache_name = "{$data['hash']}-{$data['type']}"; + // // TODO: Get or put cache or DB? + // if(! Cache::has($cache_name)) { + // $contents = call_user_func([$this->service, "fetch{$entity}s"]); + // if($contents->isEmpty()) { + // return; + // } - Cache::put($cache_name, base64_encode($contents->toJson()), 600); - } + // Cache::put($cache_name, base64_encode($contents->toJson()), 600); + // } } /** diff --git a/app/Repositories/Import/Quickbooks/CustomerRepository.php b/app/Repositories/Import/Quickbooks/CustomerRepository.php deleted file mode 100644 index 68b71b75017a..000000000000 --- a/app/Repositories/Import/Quickbooks/CustomerRepository.php +++ /dev/null @@ -1,11 +0,0 @@ -db = $db; - $this->transformer = $transfomer; - } - - public function count(): int - { - return $this->db->totalRecords($this->entity); - } - - public function all(): Collection - { - return $this->get($this->count()); - } - - public function get(int $max = 100): Collection - { - return $this->transformer->transform($this->db->fetchRecords($this->entity, $max), $this->entity); - } - - -} diff --git a/app/Services/Import/Quickbooks/Auth.php b/app/Services/Import/Quickbooks/Auth.php deleted file mode 100644 index edc3d605a8c0..000000000000 --- a/app/Services/Import/Quickbooks/Auth.php +++ /dev/null @@ -1,64 +0,0 @@ -sdk->accessToken($code, $realm); - } - - public function refreshToken(): array - { - // TODO: Get or put token in Cache or DB? - return $this->sdk->refreshToken(); - } - - public function getAuthorizationUrl(): string - { - return $this->sdk->getAuthorizationUrl(); - } - - public function getState(): string - { - return $this->sdk->getState(); - } - - public function getAccessToken(): array - { - $tokens = []; - // $token_store = new CompanyTokensRepository(); - // $tokens = $token_store->get(); - // if(empty($tokens)) { - // $token = $this->sdk->getAccessToken(); - // $access_token = $token->getAccessToken(); - // $realm = $token->getRealmID(); - // $refresh_token = $token->getRefreshToken(); - // $access_token_expires = $token->getAccessTokenExpiresAt(); - // $refresh_token_expires = $token->getRefreshTokenExpiresAt(); - // $tokens = compact('access_token', 'refresh_token','access_token_expires', 'refresh_token_expires','realm'); - // } - - return $tokens; - } - - public function getRefreshToken(): array - { - return $this->getAccessToken(); - } -} diff --git a/app/Services/Import/Quickbooks/Contracts/SdkInterface.php b/app/Services/Import/Quickbooks/Contracts/SdkInterface.php deleted file mode 100644 index 48ed4b32e84f..000000000000 --- a/app/Services/Import/Quickbooks/Contracts/SdkInterface.php +++ /dev/null @@ -1,14 +0,0 @@ -init() - ->auth(); + $this->init(); } private function init(): self { - $this->sdk = DataService::Configure([ + $config = [ 'ClientID' => config('services.quickbooks.client_id'), 'ClientSecret' => config('services.quickbooks.client_secret'), 'auth_mode' => 'oauth2', 'scope' => "com.intuit.quickbooks.accounting", - 'RedirectURI' => 'https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl', - // 'RedirectURI' => route('quickbooks.authorized'), - ]); + // 'RedirectURI' => 'https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl', + 'RedirectURI' => 'https://above-distinctly-teal.ngrok-free.app/quickbooks/authorized', + 'baseUrl' => $this->testMode ? CoreConstants::SANDBOX_DEVELOPMENT : CoreConstants::QBO_BASEURL, + ]; - // if (env('APP_DEBUG')) { - // $sdk->setLogLocation(storage_path("logs/quickbooks.log")); - // $sdk->enableLog(); - // } + $merged = array_merge($config, $this->ninjaAccessToken()); + nlog($merged); + $this->sdk = DataService::Configure($merged); + + $this->sdk->setLogLocation(storage_path("logs/quickbooks.log")); + $this->sdk->enableLog(); $this->sdk->setMinorVersion("73"); $this->sdk->throwExceptionOnError(true); @@ -52,12 +57,13 @@ class QuickbooksService return $this; } - private function auth(): self + private function ninjaAccessToken() { - $wrapper = new SdkWrapper($this->sdk); - $this->auth = new Auth($wrapper); - - return $this; + return isset($this->company->quickbooks->accessTokenKey) ? [ + 'accessTokenKey' => $this->company->quickbooks->accessTokenKey, + 'refreshTokenKey' => $this->company->quickbooks->refresh_token, + 'QBORealmID' => $this->company->quickbooks->realmID, + ] : []; } public function getSdk(): DataService @@ -65,8 +71,9 @@ class QuickbooksService return $this->sdk; } - public function getAuth(): Auth + public function getAuth(): SdkWrapper { - return $this->auth; + return new SdkWrapper($this->sdk, $this->company); } + } diff --git a/app/Services/Import/Quickbooks/Repositories/CompanyTokensRepository.php b/app/Services/Import/Quickbooks/Repositories/CompanyTokensRepository.php deleted file mode 100644 index 92aef0c8e9e3..000000000000 --- a/app/Services/Import/Quickbooks/Repositories/CompanyTokensRepository.php +++ /dev/null @@ -1,78 +0,0 @@ -company_key = $key ?? auth()->user->company()->company_key ?? null; - $this->store_key .= $key; - $this->setCompanyDbByKey(); - } - - public function save(array $tokens) - { - $this->updateAccessToken($tokens['access_token'], $tokens['access_token_expires']); - $this->updateRefreshToken($tokens['refresh_token'], $tokens['refresh_token_expires'], $tokens['realm']); - } - - - public function findByCompanyKey(): ?Company - { - return Company::where('company_key', $this->company_key)->first(); - } - - public function setCompanyDbByKey() - { - MultiDB::findAndSetDbByCompanyKey($this->company_key); - } - - public function get() - { - return $this->getAccessToken() + $this->getRefreshToken(); - } - - - protected function updateRefreshToken(string $token, string $expires, string $realm) - { - DB::table('companies') - ->where('company_key', $this->company_key) - ->update(['quickbooks_refresh_token' => $token, - 'quickbooks_realm_id' => $realm, - 'quickbooks_refresh_expires' => $expires ]); - } - - protected function updateAccessToken(string $token, string $expires) - { - - Cache::put([$this->store_key => $token], $expires); - } - - protected function getAccessToken() - { - $result = Cache::get($this->store_key); - - return $result ? ['access_token' => $result] : []; - } - - protected function getRefreshToken() - { - $result = (array) DB::table('companies') - ->select('quickbooks_refresh_token', 'quickbooks_realm_id') - ->where('company_key', $this->company_key) - ->where('quickbooks_refresh_expires', '>', now()) - ->first(); - - return $result ? array_combine(['refresh_token','realm'], array_values($result)) : []; - } - -} diff --git a/app/Services/Import/Quickbooks/SdkWrapper.php b/app/Services/Import/Quickbooks/SdkWrapper.php index 1cd8210c48ae..34fa51509a13 100644 --- a/app/Services/Import/Quickbooks/SdkWrapper.php +++ b/app/Services/Import/Quickbooks/SdkWrapper.php @@ -2,77 +2,151 @@ namespace App\Services\Import\Quickbooks; -use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface; +use Carbon\Carbon; +use App\Models\Company; +use QuickBooksOnline\API\DataService\DataService; +use QuickBooksOnline\API\Core\OAuth\OAuth2\OAuth2AccessToken; -final class SdkWrapper implements QuickbooksInterface +class SdkWrapper { public const MAXRESULTS = 10000; - private $sdk; private $entities = ['Customer','Invoice','Payment','Item']; - public function __construct($sdk) + private OAuth2AccessToken $token; + + public function __construct(public DataService $sdk, private Company $company) { - // Prep Data Services - $this->sdk = $sdk; + $this->init(); + } + + private function init(): self + { + + isset($this->company->quickbooks->accessTokenKey) ? $this->setNinjaAccessToken($this->company->quickbooks) : null; + + return $this; + } public function getAuthorizationUrl(): string { - return ($this->sdk->getOAuth2LoginHelper())->getAuthorizationCodeURL(); + return $this->sdk->getOAuth2LoginHelper()->getAuthorizationCodeURL(); } public function getState(): string { - return ($this->sdk->getOAuth2LoginHelper())->getState(); + return $this->sdk->getOAuth2LoginHelper()->getState(); } - public function getAccessToken() + public function getRefreshToken(): string { - return $this->getTokens(); + return $this->accessToken()->getRefreshToken(); + } + + public function company() + { + nlog("getting company info"); + // nlog($this->sdk->getAccessToken()); + + return $this->sdk->getCompanyInfo(); + } + /* + accessTokenKey + tokenType + refresh_token + accessTokenExpiresAt + refreshTokenExpiresAt + accessTokenValidationPeriod + refreshTokenValidationPeriod + clientID + clientSecret + realmID + baseURL + */ + public function accessTokenFromCode(string $code, string $realm): OAuth2AccessToken + { + $token = $this->sdk->getOAuth2LoginHelper()->exchangeAuthorizationCodeForToken($code, $realm); + + $this->setAccessToken($token); + + return $this->accessToken(); + } + + /** + * Set Stored NinjaAccessToken + * + * @param mixed $token_object + * @return self + */ + public function setNinjaAccessToken(mixed $token_object): self + { + $token = new OAuth2AccessToken( + config('services.quickbooks.client_id'), + config('services.quickbooks.client_secret'), + $token_object->accessTokenKey, + $token_object->refresh_token, + 3600, + 8726400 + ); + + $token->setAccessTokenExpiresAt($token_object->accessTokenExpiresAt); + $token->setRefreshTokenExpiresAt($token_object->refreshTokenExpiresAt); + $token->setAccessTokenValidationPeriodInSeconds(3600); + $token->setRefreshTokenValidationPeriodInSeconds(8726400); + + $this->setAccessToken($token); + + if($token_object->accessTokenExpiresAt < time()){ + $new_token = $this->sdk->getOAuth2LoginHelper()->refreshToken(); +nlog("getting new token"); + $this->setAccessToken($new_token); + $this->saveOAuthToken($this->accessToken()); + } + + return $this; + } + + /** + * SetsAccessToken + * + * @param OAuth2AccessToken $token + * @return self + */ + public function setAccessToken(OAuth2AccessToken $token): self + { + // $this->sdk = $this->sdk->updateOAuth2Token($token); + + $this->token = $token; +nlog("set access token"); + +nlog($token); + return $this; } - public function getRefreshToken(): array + public function accessToken(): OAuth2AccessToken { - return $this->getTokens(); + return $this->token; } - public function accessToken(string $code, string $realm): array + public function saveOAuthToken(OAuth2AccessToken $token): void { - $token = ($this->sdk->getOAuth2LoginHelper())->exchangeAuthorizationCodeForToken($code, $realm); + $obj = new \stdClass(); + $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!! + $obj->refreshTokenExpiresAt = Carbon::createFromFormat('Y/m/d H:i:s', $token->getRefreshTokenExpiresAt())->timestamp; //@phpstan-ignore-line - QB phpdoc wrong types!! - return $this->getTokens(); + $obj->realmID = $token->getRealmID(); + $obj->baseURL = $token->getBaseURL(); + + $this->company->quickbooks = $obj; + $this->company->save(); } - private function getTokens() - { - - $token = ($this->sdk->getOAuth2LoginHelper())->getAccessToken(); - return $token; - - // $access_token = $token->getAccessToken(); - // $refresh_token = $token->getRefreshToken(); - // $access_token_expires = $token->getAccessTokenExpiresAt(); - // $refresh_token_expires = $token->getRefreshTokenExpiresAt(); - - // return compact('access_token', 'refresh_token','access_token_expires', 'refresh_token_expires'); - } - - public function refreshToken(): array - { - $token = ($this->sdk->getOAuth2LoginHelper())->refreshToken(); - $this->sdk = $this->sdk->updateOAuth2Token($token); - - return $this->getTokens(); - } - - public function handleCallbacks(array $data): void - { - - } - - public function totalRecords(string $entity): int + public function totalRecords(string $entity) //returns an array not int { + nlog($this->sdk->Query("select count(*) from $entity")); return $this->sdk->Query("select count(*) from $entity"); } @@ -81,39 +155,39 @@ final class SdkWrapper implements QuickbooksInterface return (array) $this->sdk->Query($query, $start, $limit); } - public function fetchRecords(string $entity, int $max = 1000): array - { + // public function fetchRecords(string $entity, int $max = 1000): array + // { - if(!in_array($entity, $this->entities)) { - return []; - } + // if(!in_array($entity, $this->entities)) { + // return []; + // } - $records = []; - $start = 0; - $limit = 100; - try { - $total = $this->totalRecords($entity); - $total = min($max, $total); + // $records = []; + // $start = 0; + // $limit = 100; + // try { + // $total = $this->totalRecords($entity); + // $total = min($max, $total); - // Step 3 & 4: Get chunks of records until the total required records are retrieved - do { - $limit = min(self::MAXRESULTS, $total - $start); - $recordsChunk = $this->queryData("select * from $entity", $start, $limit); - if(empty($recordsChunk)) { - break; - } + // // Step 3 & 4: Get chunks of records until the total required records are retrieved + // do { + // $limit = min(self::MAXRESULTS, $total - $start); + // $recordsChunk = $this->queryData("select * from $entity", $start, $limit); + // if(empty($recordsChunk)) { + // break; + // } - $records = array_merge($records, $recordsChunk); - $start += $limit; - } while ($start < $total); - if(empty($records)) { - throw new \Exception("No records retrieved!"); - } + // $records = array_merge($records, $recordsChunk); + // $start += $limit; + // } while ($start < $total); + // if(empty($records)) { + // throw new \Exception("No records retrieved!"); + // } - } catch (\Throwable $th) { - nlog("Fetch Quickbooks API Error: {$th->getMessage()}"); - } + // } catch (\Throwable $th) { + // nlog("Fetch Quickbooks API Error: {$th->getMessage()}"); + // } - return $records; - } + // return $records; + // } } diff --git a/app/Services/Import/Quickbooks/Service.php b/app/Services/Import/Quickbooks/Service.php deleted file mode 100644 index 5002bd9e16af..000000000000 --- a/app/Services/Import/Quickbooks/Service.php +++ /dev/null @@ -1,89 +0,0 @@ -sdk = $quickbooks; - } - - public function getOAuth(): Auth - { - return new Auth($this->sdk); - } - - public function getAccessToken(): array - { - return $this->getOAuth()->getAccessToken(); - } - - public function getRefreshToken(): array - { - // TODO: Check if token is Cached otherwise fetch a new one and Cache token and expire - return $this->getAccessToken(); - } - /** - * fetch QuickBooks invoice records - * @param int $max The maximum records to fetch. Default 100 - * @return Illuminate\Support\Collection; - */ - public function fetchInvoices(int $max = 100): Collection - { - return $this->fetchRecords('Invoice', $max) ; - } - - /** - * fetch QuickBooks payment records - * @param int $max The maximum records to fetch. Default 100 - * @return Illuminate\Support\Collection; - */ - public function fetchPayments(int $max = 100): Collection - { - return $this->fetchRecords('Payment', $max) ; - } - - /** - * fetch QuickBooks product records - * @param int $max The maximum records to fetch. Default 100 - * @return Illuminate\Support\Collection; - */ - public function fetchItems(int $max = 100): Collection - { - return $this->fetchRecords('Item', $max) ; - } - - protected function fetchRecords(string $entity, $max = 100): Collection - { - return (self::RepositoryFactory($entity))->get($max); - } - - private static function RepositoryFactory(string $entity): RepositoryInterface - { - return app("\\App\\Repositories\\Import\Quickbooks\\{$entity}Repository"); - } - - /** - * fetch QuickBooks customer records - * @param int $max The maximum records to fetch. Default 100 - * @return Illuminate\Support\Collection; - */ - public function fetchCustomers(int $max = 100): Collection - { - return $this->fetchRecords('Customer', $max) ; - } - - public function totalRecords(string $entity): int - { - return (self::RepositoryFactory($entity))->count(); - } -} diff --git a/routes/web.php b/routes/web.php index 4f22c1181435..c940d0fb1d8c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -6,7 +6,6 @@ use App\Http\Controllers\Auth\ResetPasswordController; use App\Http\Controllers\Bank\NordigenController; use App\Http\Controllers\Bank\YodleeController; use App\Http\Controllers\BaseController; -use App\Http\Controllers\ImportQuickbooksController; use App\Http\Controllers\ClientPortal\ApplePayDomainController; use App\Http\Controllers\Gateways\Checkout3dsController; use App\Http\Controllers\Gateways\GoCardlessController; From b1f73f97ded2d670d02395f0a7e39ac7375ad83c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 23 Aug 2024 22:55:31 +1000 Subject: [PATCH 4/5] Qb --- .../ImportQuickbooksController.php | 17 +++- .../Quickbooks/ClientTransformer.php | 6 +- .../Import/Quickbooks/QuickbooksService.php | 8 +- app/Services/Import/Quickbooks/SdkWrapper.php | 79 +++++++++++-------- 4 files changed, 64 insertions(+), 46 deletions(-) diff --git a/app/Http/Controllers/ImportQuickbooksController.php b/app/Http/Controllers/ImportQuickbooksController.php index 9030d3c33a01..be08f66333d4 100644 --- a/app/Http/Controllers/ImportQuickbooksController.php +++ b/app/Http/Controllers/ImportQuickbooksController.php @@ -1,4 +1,13 @@ query('realmId'); - $access_token_object = $qb->getAuth()->accessTokenFromCode($request->query('code'), $realm); - $qb->getAuth()->saveOAuthToken($access_token_object); + $access_token_object = $qb->sdk()->accessTokenFromCode($request->query('code'), $realm); + $qb->sdk()->saveOAuthToken($access_token_object); return redirect(config('ninja.react_url')); @@ -43,8 +52,8 @@ class ImportQuickbooksController extends BaseController $company = $request->getCompany(); $qb = new QuickbooksService($company); - $authorizationUrl = $qb->getAuth()->getAuthorizationUrl(); - $state = $qb->getAuth()->getState(); + $authorizationUrl = $qb->sdk()->getAuthorizationUrl(); + $state = $qb->sdk()->getState(); Cache::put($state, $token, 190); diff --git a/app/Import/Transformer/Quickbooks/ClientTransformer.php b/app/Import/Transformer/Quickbooks/ClientTransformer.php index 1a1d487652aa..7ad80ad8c737 100644 --- a/app/Import/Transformer/Quickbooks/ClientTransformer.php +++ b/app/Import/Transformer/Quickbooks/ClientTransformer.php @@ -67,7 +67,7 @@ class ClientTransformer extends BaseTransformer } $transformed_data = $this->preTransform($data); - $transformed_data['contacts'][0] = $this->getContacts($data)->toArray() + ['company_id' => $this->company->id ]; + $transformed_data['contacts'][0] = $this->getContacts($data)->toArray() + ['company_id' => $this->company->id, 'user_id' => $this->company->owner()->id ]; return $transformed_data; } @@ -79,7 +79,9 @@ class ClientTransformer extends BaseTransformer 'last_name' => $this->getString($data, 'FamilyName'), 'phone' => $this->getString($data, 'PrimaryPhone.FreeFormNumber'), 'email' => $this->getString($data, 'PrimaryEmailAddr.Address'), - 'company_id' => $this->company->id + 'company_id' => $this->company->id, + 'user_id' => $this->company->owner()->id, + 'send_email' => true, ]); } diff --git a/app/Services/Import/Quickbooks/QuickbooksService.php b/app/Services/Import/Quickbooks/QuickbooksService.php index 1eb45c0cd9dc..d2a9cdd1bfb5 100644 --- a/app/Services/Import/Quickbooks/QuickbooksService.php +++ b/app/Services/Import/Quickbooks/QuickbooksService.php @@ -11,11 +11,9 @@ namespace App\Services\Import\Quickbooks; -use Carbon\Carbon; use App\Models\Company; use QuickBooksOnline\API\Core\CoreConstants; use QuickBooksOnline\API\DataService\DataService; -use QuickBooksOnline\API\Core\OAuth\OAuth2\OAuth2AccessToken; // quickbooks_realm_id // quickbooks_refresh_token @@ -40,12 +38,12 @@ class QuickbooksService 'auth_mode' => 'oauth2', 'scope' => "com.intuit.quickbooks.accounting", // 'RedirectURI' => 'https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl', - 'RedirectURI' => 'https://above-distinctly-teal.ngrok-free.app/quickbooks/authorized', + 'RedirectURI' => $this->testMode ? 'https://above-distinctly-teal.ngrok-free.app/quickbooks/authorized' : 'https://invoicing.co/quickbooks/authorized', 'baseUrl' => $this->testMode ? CoreConstants::SANDBOX_DEVELOPMENT : CoreConstants::QBO_BASEURL, ]; $merged = array_merge($config, $this->ninjaAccessToken()); - nlog($merged); + $this->sdk = DataService::Configure($merged); $this->sdk->setLogLocation(storage_path("logs/quickbooks.log")); @@ -71,7 +69,7 @@ class QuickbooksService return $this->sdk; } - public function getAuth(): SdkWrapper + public function sdk(): SdkWrapper { return new SdkWrapper($this->sdk, $this->company); } diff --git a/app/Services/Import/Quickbooks/SdkWrapper.php b/app/Services/Import/Quickbooks/SdkWrapper.php index 34fa51509a13..493719144cec 100644 --- a/app/Services/Import/Quickbooks/SdkWrapper.php +++ b/app/Services/Import/Quickbooks/SdkWrapper.php @@ -1,4 +1,13 @@ accessTokenExpiresAt < time()){ $new_token = $this->sdk->getOAuth2LoginHelper()->refreshToken(); -nlog("getting new token"); + $this->setAccessToken($new_token); $this->saveOAuthToken($this->accessToken()); } @@ -118,9 +127,7 @@ nlog("getting new token"); // $this->sdk = $this->sdk->updateOAuth2Token($token); $this->token = $token; -nlog("set access token"); -nlog($token); return $this; } @@ -144,10 +151,12 @@ nlog($token); $this->company->save(); } - public function totalRecords(string $entity) //returns an array not int + + /// Data Access /// + + public function totalRecords(string $entity): int { - nlog($this->sdk->Query("select count(*) from $entity")); - return $this->sdk->Query("select count(*) from $entity"); + return (int)$this->sdk->Query("select count(*) from $entity"); } private function queryData(string $query, int $start = 1, $limit = 100): array @@ -155,39 +164,39 @@ nlog($token); return (array) $this->sdk->Query($query, $start, $limit); } - // public function fetchRecords(string $entity, int $max = 1000): array - // { + public function fetchRecords(string $entity, int $max = 1000): array + { - // if(!in_array($entity, $this->entities)) { - // return []; - // } + if(!in_array($entity, $this->entities)) { + return []; + } - // $records = []; - // $start = 0; - // $limit = 100; - // try { - // $total = $this->totalRecords($entity); - // $total = min($max, $total); + $records = []; + $start = 0; + $limit = 100; + try { + $total = $this->totalRecords($entity); + $total = min($max, $total); - // // Step 3 & 4: Get chunks of records until the total required records are retrieved - // do { - // $limit = min(self::MAXRESULTS, $total - $start); - // $recordsChunk = $this->queryData("select * from $entity", $start, $limit); - // if(empty($recordsChunk)) { - // break; - // } + // Step 3 & 4: Get chunks of records until the total required records are retrieved + do { + $limit = min(self::MAXRESULTS, $total - $start); + $recordsChunk = $this->queryData("select * from $entity", $start, $limit); + if(empty($recordsChunk)) { + break; + } - // $records = array_merge($records, $recordsChunk); - // $start += $limit; - // } while ($start < $total); - // if(empty($records)) { - // throw new \Exception("No records retrieved!"); - // } + $records = array_merge($records, $recordsChunk); + $start += $limit; + } while ($start < $total); + if(empty($records)) { + throw new \Exception("No records retrieved!"); + } - // } catch (\Throwable $th) { - // nlog("Fetch Quickbooks API Error: {$th->getMessage()}"); - // } + } catch (\Throwable $th) { + nlog("Fetch Quickbooks API Error: {$th->getMessage()}"); + } - // return $records; - // } + return $records; + } } From 8076eb6d06bd4788cc8ce4c1fe7e126e627af469 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 24 Aug 2024 07:32:03 +1000 Subject: [PATCH 5/5] Improve filters for expenses --- app/Filters/ExpenseFilters.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/Filters/ExpenseFilters.php b/app/Filters/ExpenseFilters.php index c86a848d0b0b..ac12b6356de9 100644 --- a/app/Filters/ExpenseFilters.php +++ b/app/Filters/ExpenseFilters.php @@ -218,6 +218,11 @@ class ExpenseFilters extends QueryFilters ->whereColumn('expense_categories.id', 'expenses.category_id'), $sort_col[1]); } + if ($sort_col[0] == 'payment_date' && in_array($sort_col[1], ['asc', 'desc'])) { + return $this->builder + ->orderByRaw('ISNULL(payment_date), payment_date '. $sort_col[1]); + } + if($sort_col[0] == 'number') { return $this->builder->orderByRaw("REGEXP_REPLACE(number,'[^0-9]+','')+0 " . $dir); }