mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-10-31 17:27:33 -04:00 
			
		
		
		
	Merge pull request #9942 from turbo124/v5-develop
Sort filters for expense.payment_dates
This commit is contained in:
		
						commit
						18864e06be
					
				| @ -218,6 +218,11 @@ class ExpenseFilters extends QueryFilters | |||||||
|                     ->whereColumn('expense_categories.id', 'expenses.category_id'), $sort_col[1]); |                     ->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') { |         if($sort_col[0] == 'number') { | ||||||
|             return $this->builder->orderByRaw("REGEXP_REPLACE(number,'[^0-9]+','')+0 " . $dir); |             return $this->builder->orderByRaw("REGEXP_REPLACE(number,'[^0-9]+','')+0 " . $dir); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1,30 +1,30 @@ | |||||||
| <?php | <?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\Http\Controllers; | namespace App\Http\Controllers; | ||||||
| 
 | 
 | ||||||
| use App\Http\Requests\Quickbooks\AuthorizedQuickbooksRequest; | use App\Http\Requests\Quickbooks\AuthorizedQuickbooksRequest; | ||||||
| use Closure; |  | ||||||
| use App\Utils\Ninja; |  | ||||||
| use App\Models\Company; |  | ||||||
| use App\Libraries\MultiDB; | 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\Cache; | ||||||
| use Illuminate\Support\Facades\Validator; |  | ||||||
| use App\Http\Requests\Quickbooks\AuthQuickbooksRequest; | use App\Http\Requests\Quickbooks\AuthQuickbooksRequest; | ||||||
| use App\Services\Import\Quickbooks\QuickbooksService; | use App\Services\Import\Quickbooks\QuickbooksService; | ||||||
| 
 | 
 | ||||||
| class ImportQuickbooksController extends BaseController | class ImportQuickbooksController extends BaseController | ||||||
| { | { | ||||||
|     private array $import_entities = [ |     // private array $import_entities = [
 | ||||||
|         'client' => 'Customer', |     //     'client' => 'Customer',
 | ||||||
|         'invoice' => 'Invoice', |     //     'invoice' => 'Invoice',
 | ||||||
|         'product' => 'Item', |     //     'product' => 'Item',
 | ||||||
|         'payment' => 'Payment' |     //     'payment' => 'Payment'
 | ||||||
|     ]; |     // ];
 | ||||||
| 
 | 
 | ||||||
|     public function onAuthorized(AuthorizedQuickbooksRequest $request) |     public function onAuthorized(AuthorizedQuickbooksRequest $request) | ||||||
|     { |     { | ||||||
| @ -34,12 +34,11 @@ class ImportQuickbooksController extends BaseController | |||||||
|         $qb = new QuickbooksService($company); |         $qb = new QuickbooksService($company); | ||||||
| 
 | 
 | ||||||
|         $realm = $request->query('realmId'); |         $realm = $request->query('realmId'); | ||||||
|         $access_token_object = $qb->getAuth()->accessToken($request->query('code'), $realm); |         $access_token_object = $qb->sdk()->accessTokenFromCode($request->query('code'), $realm); | ||||||
|         nlog($access_token_object); //OAuth2AccessToken
 |         $qb->sdk()->saveOAuthToken($access_token_object); | ||||||
|         $company->quickbooks = $access_token_object; | 
 | ||||||
|         $company->save(); |         return redirect(config('ninja.react_url')); | ||||||
| 
 | 
 | ||||||
|         return response()->json(['message' => 'Success'], 200); //todo swapout for redirect to UI
 |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -53,8 +52,8 @@ class ImportQuickbooksController extends BaseController | |||||||
|         $company = $request->getCompany(); |         $company = $request->getCompany(); | ||||||
|         $qb = new QuickbooksService($company); |         $qb = new QuickbooksService($company); | ||||||
| 
 | 
 | ||||||
|         $authorizationUrl = $qb->getAuth()->getAuthorizationUrl(); |         $authorizationUrl = $qb->sdk()->getAuthorizationUrl(); | ||||||
|         $state = $qb->getAuth()->getState(); |         $state = $qb->sdk()->getState(); | ||||||
| 
 | 
 | ||||||
|         Cache::put($state, $token, 190); |         Cache::put($state, $token, 190); | ||||||
| 
 | 
 | ||||||
| @ -63,29 +62,29 @@ class ImportQuickbooksController extends BaseController | |||||||
| 
 | 
 | ||||||
|     public function preimport(string $type, string $hash) |     public function preimport(string $type, string $hash) | ||||||
|     { |     { | ||||||
|         // Check for authorization otherwise
 |         // // Check for authorization otherwise
 | ||||||
|         // Create a reference
 |         // // Create a reference
 | ||||||
|         $data = [ |         // $data = [
 | ||||||
|             'hash' => $hash, |         //     'hash' => $hash,
 | ||||||
|             'type' => $type |         //     'type' => $type
 | ||||||
|         ]; |         // ];
 | ||||||
|         $this->getData($data); |         // $this->getData($data);
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected function getData($data) |     protected function getData($data) | ||||||
|     { |     { | ||||||
| 
 | 
 | ||||||
|         $entity = $this->import_entities[$data['type']]; |         // $entity = $this->import_entities[$data['type']];
 | ||||||
|         $cache_name = "{$data['hash']}-{$data['type']}"; |         // $cache_name = "{$data['hash']}-{$data['type']}";
 | ||||||
|         // TODO: Get or put cache  or DB?
 |         // // TODO: Get or put cache  or DB?
 | ||||||
|         if(! Cache::has($cache_name)) { |         // if(! Cache::has($cache_name)) {
 | ||||||
|             $contents = call_user_func([$this->service, "fetch{$entity}s"]); |         //     $contents = call_user_func([$this->service, "fetch{$entity}s"]);
 | ||||||
|             if($contents->isEmpty()) { |         //     if($contents->isEmpty()) {
 | ||||||
|                 return; |         //         return;
 | ||||||
|             } |         //     }
 | ||||||
| 
 | 
 | ||||||
|             Cache::put($cache_name, base64_encode($contents->toJson()), 600); |         //     Cache::put($cache_name, base64_encode($contents->toJson()), 600);
 | ||||||
|         } |         // }
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -67,7 +67,7 @@ class ClientTransformer extends BaseTransformer | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $transformed_data = $this->preTransform($data); |         $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; |         return $transformed_data; | ||||||
|     } |     } | ||||||
| @ -79,7 +79,9 @@ class ClientTransformer extends BaseTransformer | |||||||
|                     'last_name'     => $this->getString($data, 'FamilyName'), |                     'last_name'     => $this->getString($data, 'FamilyName'), | ||||||
|                     'phone'         => $this->getString($data, 'PrimaryPhone.FreeFormNumber'), |                     'phone'         => $this->getString($data, 'PrimaryPhone.FreeFormNumber'), | ||||||
|                     'email'         => $this->getString($data, 'PrimaryEmailAddr.Address'), |                     '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, | ||||||
|                 ]); |                 ]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,11 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Repositories\Import\Quickbooks; |  | ||||||
| 
 |  | ||||||
| use App\Repositories\Import\Quickbooks\Repository; |  | ||||||
| use App\Repositories\Import\Quickbooks\Contracts\RepositoryInterface; |  | ||||||
| 
 |  | ||||||
| class CustomerRepository extends Repository implements RepositoryInterface |  | ||||||
| { |  | ||||||
|     protected string $entity = "Customer"; |  | ||||||
| } |  | ||||||
| @ -1,10 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Repositories\Import\Quickbooks; |  | ||||||
| 
 |  | ||||||
| use App\Repositories\Import\Quickbooks\Contracts\RepositoryInterface as QuickbooksInterface; |  | ||||||
| 
 |  | ||||||
| class InvoiceRepository extends Repository implements QuickbooksInterface |  | ||||||
| { |  | ||||||
|     protected string $entity = "Invoice"; |  | ||||||
| } |  | ||||||
| @ -1,11 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Repositories\Import\Quickbooks; |  | ||||||
| 
 |  | ||||||
| use App\Repositories\Import\Quickbooks\Repository; |  | ||||||
| use App\Repositories\Import\Quickbooks\Contracts\RepositoryInterface; |  | ||||||
| 
 |  | ||||||
| class ItemRepository extends Repository implements RepositoryInterface |  | ||||||
| { |  | ||||||
|     protected string $entity = "Item"; |  | ||||||
| } |  | ||||||
| @ -1,10 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Repositories\Import\Quickbooks; |  | ||||||
| 
 |  | ||||||
| use App\Repositories\Import\Quickbooks\Contracts\RepositoryInterface as QuickbooksInterface; |  | ||||||
| 
 |  | ||||||
| class PaymentRepository extends Repository implements QuickbooksInterface |  | ||||||
| { |  | ||||||
|     protected string $entity = "Payment"; |  | ||||||
| } |  | ||||||
| @ -1,38 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Repositories\Import\Quickbooks; |  | ||||||
| 
 |  | ||||||
| use Illuminate\Support\Collection; |  | ||||||
| use App\Repositories\Import\Quickbooks\Contracts\RepositoryInterface; |  | ||||||
| use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface; |  | ||||||
| use App\Repositories\Import\Quickbooks\Transformers\Transformer as QuickbooksTransformer; |  | ||||||
| 
 |  | ||||||
| abstract class Repository implements RepositoryInterface |  | ||||||
| { |  | ||||||
|     protected string $entity; |  | ||||||
|     protected QuickbooksInterface $db; |  | ||||||
|     protected QuickbooksTransformer $transfomer; |  | ||||||
| 
 |  | ||||||
|     public function __construct(QuickbooksInterface $db, QuickbooksTransformer $transfomer) |  | ||||||
|     { |  | ||||||
|         $this->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); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| @ -1,64 +0,0 @@ | |||||||
| <?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\Import\Quickbooks; |  | ||||||
| 
 |  | ||||||
| final class Auth |  | ||||||
| { |  | ||||||
|     public function __construct(private SdkWrapper $sdk) |  | ||||||
|     { |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function accessToken(string $code, string $realm): array |  | ||||||
|     { |  | ||||||
|         // TODO: Get or put token in Cache or DB?
 |  | ||||||
|         return $this->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(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,14 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Services\Import\Quickbooks\Contracts; |  | ||||||
| 
 |  | ||||||
| interface SdkInterface |  | ||||||
| { |  | ||||||
|     public function getAuthorizationUrl(): string; |  | ||||||
|     public function accessToken(string $code, string $realm): array; |  | ||||||
|     public function refreshToken(): array; |  | ||||||
|     public function getAccessToken(); |  | ||||||
|     public function getRefreshToken(): array; |  | ||||||
|     public function totalRecords(string $entity): int; |  | ||||||
|     public function fetchRecords(string $entity, int $max): array; |  | ||||||
| } |  | ||||||
| @ -12,6 +12,7 @@ | |||||||
| namespace App\Services\Import\Quickbooks; | namespace App\Services\Import\Quickbooks; | ||||||
| 
 | 
 | ||||||
| use App\Models\Company; | use App\Models\Company; | ||||||
|  | use QuickBooksOnline\API\Core\CoreConstants; | ||||||
| use QuickBooksOnline\API\DataService\DataService; | use QuickBooksOnline\API\DataService\DataService; | ||||||
| 
 | 
 | ||||||
| // quickbooks_realm_id
 | // quickbooks_realm_id
 | ||||||
| @ -19,32 +20,34 @@ use QuickBooksOnline\API\DataService\DataService; | |||||||
| // quickbooks_refresh_expires
 | // quickbooks_refresh_expires
 | ||||||
| class QuickbooksService | class QuickbooksService | ||||||
| { | { | ||||||
|     private DataService $sdk; |     public DataService $sdk; | ||||||
| 
 | 
 | ||||||
|     private Auth $auth; |     private bool $testMode = true; | ||||||
| 
 | 
 | ||||||
|     public function __construct(private Company $company) |     public function __construct(private Company $company) | ||||||
|     { |     { | ||||||
|         $this->init() |         $this->init(); | ||||||
|             ->auth(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function init(): self |     private function init(): self | ||||||
|     { |     { | ||||||
| 
 | 
 | ||||||
|         $this->sdk = DataService::Configure([ |         $config = [ | ||||||
|             'ClientID' => config('services.quickbooks.client_id'), |             'ClientID' => config('services.quickbooks.client_id'), | ||||||
|             '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' => 'https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl', |             // 'RedirectURI' => 'https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl',
 | ||||||
|             // 'RedirectURI' => route('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, | ||||||
|  |         ]; | ||||||
| 
 | 
 | ||||||
|         // if (env('APP_DEBUG')) {
 |         $merged = array_merge($config, $this->ninjaAccessToken()); | ||||||
|         //     $sdk->setLogLocation(storage_path("logs/quickbooks.log"));
 |          | ||||||
|         //     $sdk->enableLog();
 |         $this->sdk = DataService::Configure($merged); | ||||||
|         // }
 | 
 | ||||||
|  |         $this->sdk->setLogLocation(storage_path("logs/quickbooks.log")); | ||||||
|  |         $this->sdk->enableLog(); | ||||||
| 
 | 
 | ||||||
|         $this->sdk->setMinorVersion("73"); |         $this->sdk->setMinorVersion("73"); | ||||||
|         $this->sdk->throwExceptionOnError(true); |         $this->sdk->throwExceptionOnError(true); | ||||||
| @ -52,12 +55,13 @@ class QuickbooksService | |||||||
|         return $this; |         return $this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function auth(): self |     private function ninjaAccessToken() | ||||||
|     { |     { | ||||||
|         $wrapper = new SdkWrapper($this->sdk); |         return isset($this->company->quickbooks->accessTokenKey) ? [ | ||||||
|         $this->auth = new Auth($wrapper); |             'accessTokenKey' => $this->company->quickbooks->accessTokenKey, | ||||||
| 
 |             'refreshTokenKey' => $this->company->quickbooks->refresh_token, | ||||||
|         return $this; |             'QBORealmID' => $this->company->quickbooks->realmID, | ||||||
|  |         ] : []; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function getSdk(): DataService |     public function getSdk(): DataService | ||||||
| @ -65,8 +69,9 @@ class QuickbooksService | |||||||
|         return $this->sdk; |         return $this->sdk; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function getAuth(): Auth |     public function sdk(): SdkWrapper | ||||||
|     { |     { | ||||||
|         return $this->auth; |         return new SdkWrapper($this->sdk, $this->company); | ||||||
|     } |     } | ||||||
|  |      | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,78 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Services\Import\Quickbooks\Repositories; |  | ||||||
| 
 |  | ||||||
| use App\Models\Company; |  | ||||||
| use App\Libraries\MultiDB; |  | ||||||
| use Illuminate\Support\Facades\DB; |  | ||||||
| use Illuminate\Support\Facades\Cache; |  | ||||||
| 
 |  | ||||||
| class CompanyTokensRepository |  | ||||||
| { |  | ||||||
|     private $company_key; |  | ||||||
|     private $store_key = "quickbooks-token"; |  | ||||||
| 
 |  | ||||||
|     public function __construct(string $key = null) |  | ||||||
|     { |  | ||||||
|         $this->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)) : []; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| @ -1,79 +1,162 @@ | |||||||
| <?php | <?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\Import\Quickbooks; | 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; |     public const MAXRESULTS = 10000; | ||||||
| 
 | 
 | ||||||
|     private $sdk; |  | ||||||
|     private $entities = ['Customer','Invoice','Payment','Item']; |     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->init(); | ||||||
|         $this->sdk = $sdk; |     } | ||||||
|  | 
 | ||||||
|  |     private function init(): self | ||||||
|  |     { | ||||||
|  |          | ||||||
|  |         isset($this->company->quickbooks->accessTokenKey) ? $this->setNinjaAccessToken($this->company->quickbooks) : null; | ||||||
|  | 
 | ||||||
|  |         return $this; | ||||||
|  | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function getAuthorizationUrl(): string |     public function getAuthorizationUrl(): string | ||||||
|     { |     { | ||||||
|         return ($this->sdk->getOAuth2LoginHelper())->getAuthorizationCodeURL(); |         return $this->sdk->getOAuth2LoginHelper()->getAuthorizationCodeURL(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function getState(): string |     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 getRefreshToken(): array |     public function company() | ||||||
|     { |     { | ||||||
|         return $this->getTokens(); |         nlog("getting company info"); | ||||||
|     } |         // nlog($this->sdk->getAccessToken());
 | ||||||
| 
 | 
 | ||||||
|     public function accessToken(string $code, string $realm): array |         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); |         $token = $this->sdk->getOAuth2LoginHelper()->exchangeAuthorizationCodeForToken($code, $realm); | ||||||
| 
 | 
 | ||||||
|         return $this->getTokens(); |         $this->setAccessToken($token); | ||||||
|  | 
 | ||||||
|  |         return $this->accessToken(); | ||||||
|     } |     } | ||||||
| 
 |          | ||||||
|     private function getTokens() |     /** | ||||||
|  |      * 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 = ($this->sdk->getOAuth2LoginHelper())->getAccessToken(); |         $token->setAccessTokenExpiresAt($token_object->accessTokenExpiresAt); | ||||||
|         return $token; |         $token->setRefreshTokenExpiresAt($token_object->refreshTokenExpiresAt); | ||||||
|  |         $token->setAccessTokenValidationPeriodInSeconds(3600); | ||||||
|  |         $token->setRefreshTokenValidationPeriodInSeconds(8726400); | ||||||
| 
 | 
 | ||||||
|         // $access_token = $token->getAccessToken();
 |         $this->setAccessToken($token); | ||||||
|         // $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');
 |         if($token_object->accessTokenExpiresAt < time()){ | ||||||
|  |             $new_token = $this->sdk->getOAuth2LoginHelper()->refreshToken(); | ||||||
|  | 
 | ||||||
|  |             $this->setAccessToken($new_token); | ||||||
|  |             $this->saveOAuthToken($this->accessToken()); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         return $this; | ||||||
|     } |     } | ||||||
| 
 |      | ||||||
|     public function refreshToken(): array |     /** | ||||||
|  |      * SetsAccessToken | ||||||
|  |      * | ||||||
|  |      * @param  OAuth2AccessToken $token | ||||||
|  |      * @return self | ||||||
|  |      */ | ||||||
|  |     public function setAccessToken(OAuth2AccessToken $token): self | ||||||
|     { |     { | ||||||
|         $token = ($this->sdk->getOAuth2LoginHelper())->refreshToken(); |         // $this->sdk = $this->sdk->updateOAuth2Token($token);
 | ||||||
|         $this->sdk = $this->sdk->updateOAuth2Token($token); |  | ||||||
| 
 | 
 | ||||||
|         return $this->getTokens(); |         $this->token = $token; | ||||||
|  | 
 | ||||||
|  |         return $this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function handleCallbacks(array $data): void |     public function accessToken(): OAuth2AccessToken | ||||||
|     { |     { | ||||||
| 
 |         return $this->token; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function saveOAuthToken(OAuth2AccessToken $token): void | ||||||
|  |     { | ||||||
|  |         $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!!
 | ||||||
|  | 
 | ||||||
|  |         $obj->realmID = $token->getRealmID(); | ||||||
|  |         $obj->baseURL = $token->getBaseURL(); | ||||||
|  | 
 | ||||||
|  |         $this->company->quickbooks = $obj; | ||||||
|  |         $this->company->save(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     /// Data Access ///
 | ||||||
|  | 
 | ||||||
|     public function totalRecords(string $entity): int |     public function totalRecords(string $entity): int | ||||||
|     { |     { | ||||||
|         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 |     private function queryData(string $query, int $start = 1, $limit = 100): array | ||||||
|  | |||||||
| @ -1,89 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| namespace App\Services\Import\Quickbooks; |  | ||||||
| 
 |  | ||||||
| use Illuminate\Support\Collection; |  | ||||||
| use Illuminate\Support\Facades\Cache; |  | ||||||
| use App\Services\Import\Quickbooks\Auth; |  | ||||||
| use App\Repositories\Import\Quickbooks\Contracts\RepositoryInterface; |  | ||||||
| use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface; |  | ||||||
| 
 |  | ||||||
| final class Service |  | ||||||
| { |  | ||||||
|     private QuickbooksInterface $sdk; |  | ||||||
| 
 |  | ||||||
|     public function __construct(QuickbooksInterface $quickbooks) |  | ||||||
|     { |  | ||||||
|         $this->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(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -5319,6 +5319,8 @@ $lang = array( | |||||||
|     'one_page_checkout' => 'One-Page Checkout', |     'one_page_checkout' => 'One-Page Checkout', | ||||||
|     'one_page_checkout_help' => 'Enable the new single page payment flow', |     'one_page_checkout_help' => 'Enable the new single page payment flow', | ||||||
|     'applies_to' => 'Applies To', |     'applies_to' => 'Applies To', | ||||||
|  |     'accept_purchase_order' => 'Accept Purchase Order', | ||||||
|  |     'round_to_seconds' => 'Round To Seconds', | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| return $lang; | return $lang; | ||||||
|  | |||||||
| @ -4847,7 +4847,7 @@ E-mail: :email<b><br><b>', | |||||||
|     'failed' => 'Mislukt', |     'failed' => 'Mislukt', | ||||||
|     'client_contacts' => 'Klantencontacten', |     'client_contacts' => 'Klantencontacten', | ||||||
|     'sync_from' => 'Synchroniseren van', |     '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', |     'gateway_payment_text_no_invoice' => 'Betaling zonder factuur voor bedrag :amount voor opdrachtgever :client', | ||||||
|     'click_to_variables' => 'Klik hier om alle variabelen te zien.', |     'click_to_variables' => 'Klik hier om alle variabelen te zien.', | ||||||
|     'ship_to' => 'Verzend naar', |     'ship_to' => 'Verzend naar', | ||||||
| @ -5314,6 +5314,11 @@ E-mail: :email<b><br><b>', | |||||||
|     'comments_only' => 'Alleen opmerkingen', |     'comments_only' => 'Alleen opmerkingen', | ||||||
|     'payment_balance_on_file' => 'Huidig saldo', |     'payment_balance_on_file' => 'Huidig saldo', | ||||||
|     'ubl_email_attachment_help' => 'Voor meer e-factuurinstellingen, navigeer naar :here', |     '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; | return $lang; | ||||||
|  | |||||||
| @ -6,7 +6,6 @@ use App\Http\Controllers\Auth\ResetPasswordController; | |||||||
| use App\Http\Controllers\Bank\NordigenController; | use App\Http\Controllers\Bank\NordigenController; | ||||||
| use App\Http\Controllers\Bank\YodleeController; | use App\Http\Controllers\Bank\YodleeController; | ||||||
| use App\Http\Controllers\BaseController; | use App\Http\Controllers\BaseController; | ||||||
| use App\Http\Controllers\ImportQuickbooksController; |  | ||||||
| use App\Http\Controllers\ClientPortal\ApplePayDomainController; | use App\Http\Controllers\ClientPortal\ApplePayDomainController; | ||||||
| use App\Http\Controllers\Gateways\Checkout3dsController; | use App\Http\Controllers\Gateways\Checkout3dsController; | ||||||
| use App\Http\Controllers\Gateways\GoCardlessController; | use App\Http\Controllers\Gateways\GoCardlessController; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user