From 9c225458c665cdef95bea420275265fccf702970 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 21 Aug 2024 13:04:09 +1000 Subject: [PATCH 1/9] Fixes for QB pr --- app/Factory/QuickbooksSDKFactory.php | 63 ------- .../ImportQuickbooksController.php | 158 +++++++++--------- app/Models/Company.php | 2 + app/Providers/QuickbooksServiceProvider.php | 84 ---------- app/Services/Import/Quickbooks/Auth.php | 68 ++++---- app/Transformers/CompanyTransformer.php | 1 + config/app.php | 1 - config/services.php | 14 +- ...8_02_144614_alter_companies_quickbooks.php | 10 +- routes/api.php | 6 + routes/web.php | 3 +- .../Import/Quickbooks/SdkWrapperTest.php | 5 +- 12 files changed, 149 insertions(+), 266 deletions(-) delete mode 100644 app/Factory/QuickbooksSDKFactory.php delete mode 100644 app/Providers/QuickbooksServiceProvider.php diff --git a/app/Factory/QuickbooksSDKFactory.php b/app/Factory/QuickbooksSDKFactory.php deleted file mode 100644 index fcd9ebf70ca1..000000000000 --- a/app/Factory/QuickbooksSDKFactory.php +++ /dev/null @@ -1,63 +0,0 @@ -company(); - - $token_store = (new CompanyTokensRepository($company->company_key)); - $tokens = array_filter($token_store->get()); - if(!empty($tokens)) { - $keys = ['refreshTokenKey','QBORealmID']; - if(array_key_exists('access_token', $tokens)) { - $keys = array_merge(['accessTokenKey'] ,$keys); - } - - $tokens = array_combine($keys, array_values($tokens)); - } - } - - $config = $tokens + config('services.quickbooks.settings') + [ - 'state' => Str::random(12) - ]; - $sdk = DataService::Configure($config); - if (env('APP_DEBUG')) { - $sdk->setLogLocation(storage_path("logs/quickbooks.log")); - $sdk->enableLog(); - } - - $sdk->setMinorVersion("73"); - $sdk->throwExceptionOnError(true); - if(array_key_exists('refreshTokenKey', $config) && !array_key_exists('accessTokenKey', $config)) - { - $tokens = ($sdk->getOAuth2LoginHelper())->refreshToken(); - $sdk = $sdk->updateOAuth2Token($tokens); - $tokens = ($sdk->getOAuth2LoginHelper())->getAccessToken(); - $access_token = $tokens->getAccessToken(); - $realm = $tokens->getRealmID(); - $refresh_token = $tokens->getRefreshToken(); - $access_token_expires = $tokens->getAccessTokenExpiresAt(); - $refresh_token_expires = $tokens->getRefreshTokenExpiresAt(); - $tokens = compact('access_token', 'refresh_token','access_token_expires', 'refresh_token_expires','realm'); - $token_store->save($tokens); - } - - return $sdk; - } -} diff --git a/app/Http/Controllers/ImportQuickbooksController.php b/app/Http/Controllers/ImportQuickbooksController.php index 7419de3e57cc..09e75dc81f3c 100644 --- a/app/Http/Controllers/ImportQuickbooksController.php +++ b/app/Http/Controllers/ImportQuickbooksController.php @@ -5,18 +5,21 @@ namespace App\Http\Controllers; 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 Illuminate\Support\Facades\Cache; use App\Jobs\Import\QuickbooksIngest; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Validator; -use App\Services\Import\Quickbooks\Service as QuickbooksService; +use App\Http\Requests\Quickbooks\AuthQuickbooksRequest; +use App\Services\Import\Quickbooks\QuickbooksService; class ImportQuickbooksController extends BaseController { - protected QuickbooksService $service; + // protected QuickbooksService $service; + private $import_entities = [ 'client' => 'Customer', 'invoice' => 'Invoice', @@ -24,79 +27,79 @@ class ImportQuickbooksController extends BaseController 'payment' => 'Payment' ]; - public function __construct(QuickbooksService $service) { - parent::__construct(); + // public function __construct(QuickbooksService $service) { + // parent::__construct(); - $this->service = $service; - $this->middleware( - function (Request $request, Closure $next) { + // $this->service = $service; + // $this->middleware( + // function (Request $request, Closure $next) { - // Check for the required query parameters - if (!$request->has(['code', 'state', 'realmId'])) { - return abort(400,'Unauthorized'); - } + // // Check for the required query parameters + // if (!$request->has(['code', 'state', 'realmId'])) { + // return abort(400,'Unauthorized'); + // } - $rules = [ - 'state' => [ - 'required', - 'valid' => function ($attribute, $value, $fail) { - if (!Cache::has($value)) { - $fail('The state is invalid.'); - } - }, - ] - ]; - // Custom error messages - $messages = [ - 'state.required' => 'The state is required.', - 'state.valid' => 'state token not valid' - ]; - // Perform the validation - $validator = Validator::make($request->all(), $rules, $messages); - if ($validator->fails()) { - // If validation fails, redirect back with errors and input - return redirect('/') - ->withErrors($validator) - ->withInput(); - } + // $rules = [ + // 'state' => [ + // 'required', + // 'valid' => function ($attribute, $value, $fail) { + // if (!Cache::has($value)) { + // $fail('The state is invalid.'); + // } + // }, + // ] + // ]; + // // Custom error messages + // $messages = [ + // 'state.required' => 'The state is required.', + // 'state.valid' => 'state token not valid' + // ]; + // // Perform the validation + // $validator = Validator::make($request->all(), $rules, $messages); + // if ($validator->fails()) { + // // If validation fails, redirect back with errors and input + // return redirect('/') + // ->withErrors($validator) + // ->withInput(); + // } - $token = Cache::pull($request->state); - $request->merge(['company' => Cache::get($token) ]); + // $token = Cache::pull($request->state); + // $request->merge(['company' => Cache::get($token) ]); - return $next($request); - } - )->only('onAuthorized'); - $this->middleware( - function ( Request $request, Closure $next) { - $rules = [ - 'token' => [ - 'required', - 'valid' => function ($attribute, $value, $fail) { - if (!Cache::has($value) || (!Company::where('company_key', (Cache::get($value))['company_key'])->exists() )) { - $fail('The company is invalid.'); - } - }, - ] - ]; - // Custom error messages - $messages = [ - 'token.required' => 'The token is required.', - 'token.valid' => 'Token note valid!' - ]; - // Perform the validation - $validator = Validator::make(['token' => $request->token ], $rules, $messages); - if ($validator->fails()) { - return redirect() - ->back() - ->withErrors($validator) - ->withInput(); - } + // return $next($request); + // } + // )->only('onAuthorized'); + // $this->middleware( + // function ( Request $request, Closure $next) { + // $rules = [ + // 'token' => [ + // 'required', + // 'valid' => function ($attribute, $value, $fail) { + // if (!Cache::has($value) || (!Company::where('company_key', (Cache::get($value))['company_key'])->exists() )) { + // $fail('The company is invalid.'); + // } + // }, + // ] + // ]; + // // Custom error messages + // $messages = [ + // 'token.required' => 'The token is required.', + // 'token.valid' => 'Token note valid!' + // ]; + // // Perform the validation + // $validator = Validator::make(['token' => $request->token ], $rules, $messages); + // if ($validator->fails()) { + // return redirect() + // ->back() + // ->withErrors($validator) + // ->withInput(); + // } - //If validation passes, proceed to the next middleware/controller - return $next($request); - } - )->only('authorizeQuickbooks'); - } + // //If validation passes, proceed to the next middleware/controller + // return $next($request); + // } + // )->only('authorizeQuickbooks'); + // } public function onAuthorized(Request $request) { @@ -114,12 +117,15 @@ class ImportQuickbooksController extends BaseController * * @return bool */ - public function authorizeQuickbooks(Request $request) + public function authorizeQuickbooks(AuthQuickbooksRequest $request, string $token) { - $token = $request->token; - $auth = $this->service->getOAuth(); - $authorizationUrl = $auth->getAuthorizationUrl(); - $state = $auth->getState(); + + MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']); + $company = $request->getCompany(); + $qb = new QuickbooksService($company); + + $authorizationUrl = $qb->getAuth()->getAuthorizationUrl(); + $state = $qb->getAuth()->getState(); Cache::put($state, $token, 190); @@ -186,7 +192,7 @@ class ImportQuickbooksController extends BaseController $this->preimport($type, $hash); } /** @var \App\Models\User $user */ - $user = auth()->user() ?? Auth::loginUsingId(60); + // $user = auth()->user() ?? Auth::loginUsingId(60); $data = ['import_types' => $request->input('import_types') ] + compact('hash'); if (Ninja::isHosted()) { QuickbooksIngest::dispatch( $data , $user->company() ); diff --git a/app/Models/Company.php b/app/Models/Company.php index cdfde34fe883..b1d504ca6943 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -118,6 +118,7 @@ use Laracasts\Presenter\PresentableTrait; * @property string|null $smtp_port * @property string|null $smtp_encryption * @property string|null $smtp_local_domain + * @property string|null $quickbooks * @property boolean $smtp_verify_peer * @property-read \App\Models\Account $account * @property-read \Illuminate\Database\Eloquent\Collection $activities @@ -390,6 +391,7 @@ class Company extends BaseModel 'smtp_username' => 'encrypted', 'smtp_password' => 'encrypted', 'e_invoice' => 'object', + 'quickbooks' => 'object', ]; protected $with = []; diff --git a/app/Providers/QuickbooksServiceProvider.php b/app/Providers/QuickbooksServiceProvider.php deleted file mode 100644 index 0a3777eb3f80..000000000000 --- a/app/Providers/QuickbooksServiceProvider.php +++ /dev/null @@ -1,84 +0,0 @@ -app->bind(QuickbooksInterface::class, function ($app) { - return new QuickbooksSDKWrapper(QuickbooksSDKFactory::create()); - }); - - // Register SDKWrapper with DataService dependency - $this->app->singleton(QuickbooksService::class, function ($app) { - return new QuickbooksService($app->make(QuickbooksInterface::class)); - }); - - $this->app->singleton(QuickbooksTransformer::class,QuickbooksTransformer::class); - } - /** - * Bootstrap services. - * - * @return void - */ - public function boot() - { - $this->registerRoutes(); - $this->registerConfig(); - } - - protected function registerConfig() { - config()->set( 'services.quickbooks' , - ['settings' => [ - 'auth_mode' => 'oauth2', - '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_ENV')) - ], - 'debug' => env('APP_DEBUG') || env('APP_ENV') - ] - ); - } - - /** - * Register custom routes. - * - * @return void - */ - protected function registerRoutes() - { - Route::middleware('web') - ->namespace($this->app->getNamespace() . 'Http\Controllers') - ->group(function () { - Route::get('quickbooks/authorize/{token}', [ImportQuickbooksController::class, 'authorizeQuickbooks'])->name('authorize.quickbooks'); - Route::get('quickbooks/authorized', [ImportQuickbooksController::class, 'onAuthorized'])->name('authorized.quickbooks'); - }); - Route::prefix('api/v1') - ->middleware('api') - ->namespace($this->app->getNamespace() . 'Http\Controllers') - ->group(function () { - Route::post('import/quickbooks', [ImportQuickbooksController::class, 'import'])->name('import.quickbooks'); - }); - } -} diff --git a/app/Services/Import/Quickbooks/Auth.php b/app/Services/Import/Quickbooks/Auth.php index a8f08c7cfdf0..a6df31ba60a2 100644 --- a/app/Services/Import/Quickbooks/Auth.php +++ b/app/Services/Import/Quickbooks/Auth.php @@ -1,66 +1,72 @@ sdk = $quickbooks; +{ + public function __construct(private DataService $sdk) + { } - public function accessToken(string $code, string $realm ) : array + public function accessToken(string $code, string $realm): array { - // TODO: Get or put token in Cache or DB? + // TODO: Get or put token in Cache or DB? return $this->sdk->accessToken($code, $realm); } - public function refreshToken() : array + public function refreshToken(): array { // TODO: Get or put token in Cache or DB? return $this->sdk->refreshToken(); } - public function getAuthorizationUrl(): string + public function getAuthorizationUrl(): string { return $this->sdk->getAuthorizationUrl(); } - public function getState() : string + public function getState(): string { return $this->sdk->getState(); } public function saveTokens($key, $tokens) { - $token_store = new CompanyTokensRepository($key); - $token_store->save($tokens); + // $token_store = new CompanyTokensRepository($key); + // $token_store->save($tokens); } - public function getAccessToken() : array + public function getAccessToken(): array { - $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'); - } - + $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 + public function getRefreshToken(): array { return $this->getAccessToken(); } -} \ No newline at end of file +} diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index f13090bb63de..71148d81f191 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -212,6 +212,7 @@ class CompanyTransformer extends EntityTransformer 'smtp_local_domain' => (string)$company->smtp_local_domain ?? '', 'smtp_verify_peer' => (bool)$company->smtp_verify_peer, 'e_invoice' => $company->e_invoice ?: new \stdClass(), + 'quickbooks' => $company->quickbooks ?: new \stdClass(), ]; } diff --git a/config/app.php b/config/app.php index ecb68869456b..8fe0ab11f51b 100644 --- a/config/app.php +++ b/config/app.php @@ -201,7 +201,6 @@ return [ App\Providers\ClientPortalServiceProvider::class, App\Providers\NinjaTranslationServiceProvider::class, App\Providers\StaticServiceProvider::class, - App\Providers\QuickbooksServiceProvider::class ], /* diff --git a/config/services.php b/config/services.php index ae434aba6575..6bb8ecaf956c 100644 --- a/config/services.php +++ b/config/services.php @@ -120,5 +120,17 @@ return [ 'chorus' => [ 'client_id' => env('CHORUS_CLIENT_ID', false), '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) + ], ]; diff --git a/database/migrations/2024_08_02_144614_alter_companies_quickbooks.php b/database/migrations/2024_08_02_144614_alter_companies_quickbooks.php index 0050e38da824..627239cc9de1 100644 --- a/database/migrations/2024_08_02_144614_alter_companies_quickbooks.php +++ b/database/migrations/2024_08_02_144614_alter_companies_quickbooks.php @@ -12,11 +12,9 @@ return new class extends Migration * @return void */ public function up() - { + { Schema::table('companies', function (Blueprint $table) { - $table->string('quickbooks_realm_id')->nullable(); - $table->string('quickbooks_refresh_token')->nullable(); - $table->dateTime('quickbooks_refresh_expires')->nullable(); + $table->text('quickbooks')->nullable(); }); } @@ -27,8 +25,6 @@ return new class extends Migration */ public function down() { - Schema::table('companies', function (Blueprint $table) { - $table->dropColumn(['quickbooks_realm_id', 'quickbooks_refresh_token','quickbooks_refresh_expires']); - }); + } }; diff --git a/routes/api.php b/routes/api.php index ddcdc37b718d..bbd67c59de73 100644 --- a/routes/api.php +++ b/routes/api.php @@ -427,6 +427,9 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('yodlee/status/{account_number}', [YodleeController::class, 'accountStatus']); // @todo @turbo124 check route-path?! Route::get('nordigen/institutions', [NordigenController::class, 'institutions'])->name('nordigen.institutions'); + + Route::post('import/quickbooks', [ImportQuickbooksController::class, 'import'])->name('import.quickbooks'); + }); Route::post('api/v1/sms_reset', [TwilioController::class, 'generate2faResetCode'])->name('sms_reset.generate')->middleware('throttle:3,1'); @@ -460,4 +463,7 @@ Route::post('api/v1/yodlee/balance', [YodleeController::class, 'balanceWebhook'] Route::get('api/v1/protected_download/{hash}', [ProtectedDownloadController::class, 'index'])->name('protected_download')->middleware('throttle:300,1'); Route::post('api/v1/ppcp/webhook', [PayPalPPCPPaymentDriver::class, 'processWebhookRequest'])->middleware('throttle:1000,1'); +Route::get('quickbooks/authorize/{token}', [ImportQuickbooksController::class, 'authorizeQuickbooks'])->name('quickbooks.authorize'); +Route::get('quickbooks/authorized', [ImportQuickbooksController::class, 'onAuthorized'])->name('quickbooks.authorized'); + Route::fallback([BaseController::class, 'notFound'])->middleware('throttle:404'); diff --git a/routes/web.php b/routes/web.php index 78ac4dbfe52b..4f22c1181435 100644 --- a/routes/web.php +++ b/routes/web.php @@ -51,4 +51,5 @@ Route::get('mollie/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', [Mol Route::get('gocardless/ibp_redirect/{company_key}/{company_gateway_id}/{hash}', [GoCardlessController::class, 'ibpRedirect'])->middleware('domain_db')->name('gocardless.ibp_redirect'); Route::get('.well-known/apple-developer-merchantid-domain-association', [ApplePayDomainController::class, 'showAppleMerchantId']); -Broadcast::routes(['middleware' => ['token_auth']]); + +\Illuminate\Support\Facades\Broadcast::routes(['middleware' => ['token_auth']]); diff --git a/tests/Unit/Services/Import/Quickbooks/SdkWrapperTest.php b/tests/Unit/Services/Import/Quickbooks/SdkWrapperTest.php index a76da2720fe1..a7afa36e9758 100644 --- a/tests/Unit/Services/Import/Quickbooks/SdkWrapperTest.php +++ b/tests/Unit/Services/Import/Quickbooks/SdkWrapperTest.php @@ -18,10 +18,11 @@ class SdkWrapperTest extends TestCase { parent::setUp(); - $this->sdkMock = Mockery::mock(sdtClass::class); + + $this->sdkMock = Mockery::mock(\stdClass::class); $this->sdk = new QuickbookSDK($this->sdkMock); - + $this->markTestSkipped('no resource'); } function testIsInstanceOf() { From 9af3ffb77c2621a61c406154f67b05638b3cf61e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 21 Aug 2024 13:18:21 +1000 Subject: [PATCH 2/9] Refactor --- app/Services/Import/Quickbooks/Auth.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/Services/Import/Quickbooks/Auth.php b/app/Services/Import/Quickbooks/Auth.php index a6df31ba60a2..ec0442fba4a2 100644 --- a/app/Services/Import/Quickbooks/Auth.php +++ b/app/Services/Import/Quickbooks/Auth.php @@ -11,11 +11,9 @@ namespace App\Services\Import\Quickbooks; -use QuickBooksOnline\API\DataService\DataService; - final class Auth { - public function __construct(private DataService $sdk) + public function __construct(private SdkWrapper $sdk) { } From f8e06d7ca313c07caff8b2f2f8911eb01894d1c5 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 21 Aug 2024 13:22:46 +1000 Subject: [PATCH 3/9] Refactor QB --- .../Quickbooks/AuthQuickbooksRequest.php | 69 ++++++++++++++++++ .../Import/Quickbooks/QuickbooksService.php | 72 +++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 app/Http/Requests/Quickbooks/AuthQuickbooksRequest.php create mode 100644 app/Services/Import/Quickbooks/QuickbooksService.php diff --git a/app/Http/Requests/Quickbooks/AuthQuickbooksRequest.php b/app/Http/Requests/Quickbooks/AuthQuickbooksRequest.php new file mode 100644 index 000000000000..9e462ce6e319 --- /dev/null +++ b/app/Http/Requests/Quickbooks/AuthQuickbooksRequest.php @@ -0,0 +1,69 @@ +getTokenContent()); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules(): array + { + return [ + // + ]; + } + + /** + * Resolve one-time token instance. + * + * @return mixed + */ + public function getTokenContent() + { + if ($this->state) { + $this->token = $this->state; + } + + $data = Cache::get($this->token); + + return $data; + } + + public function getContact(): ?User + { + return User::findOrFail($this->getTokenContent()['user_id']); + } + + public function getCompany(): ?Company + { + return Company::where('company_key', $this->getTokenContent()['company_key'])->firstOrFail(); + } +} diff --git a/app/Services/Import/Quickbooks/QuickbooksService.php b/app/Services/Import/Quickbooks/QuickbooksService.php new file mode 100644 index 000000000000..8832ee15713f --- /dev/null +++ b/app/Services/Import/Quickbooks/QuickbooksService.php @@ -0,0 +1,72 @@ +init() + ->auth(); + } + + private function init(): self + { + + $this->sdk = DataService::Configure([ + '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'), + ]); + + // if (env('APP_DEBUG')) { + // $sdk->setLogLocation(storage_path("logs/quickbooks.log")); + // $sdk->enableLog(); + // } + + $this->sdk->setMinorVersion("73"); + $this->sdk->throwExceptionOnError(true); + + return $this; + } + + private function auth(): self + { + $wrapper = new SdkWrapper($this->sdk); + $this->auth = new Auth($wrapper); + + return $this; + } + + public function getSdk(): DataService + { + return $this->sdk; + } + + public function getAuth(): Auth + { + return $this->auth; + } +} From c231c9186feddfa83a2158989583360145d74ffd Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 21 Aug 2024 15:07:52 +1000 Subject: [PATCH 4/9] Refactor for quickbooks --- .../ImportQuickbooksController.php | 100 +++--------------- .../AuthorizedQuickbooksRequest.php | 69 ++++++++++++ app/Services/Import/Quickbooks/SdkWrapper.php | 16 +-- 3 files changed, 95 insertions(+), 90 deletions(-) create mode 100644 app/Http/Requests/Quickbooks/AuthorizedQuickbooksRequest.php diff --git a/app/Http/Controllers/ImportQuickbooksController.php b/app/Http/Controllers/ImportQuickbooksController.php index 09e75dc81f3c..558188c91aaf 100644 --- a/app/Http/Controllers/ImportQuickbooksController.php +++ b/app/Http/Controllers/ImportQuickbooksController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Http\Requests\Quickbooks\AuthorizedQuickbooksRequest; use \Closure; use App\Utils\Ninja; use App\Models\Company; @@ -18,98 +19,31 @@ use App\Services\Import\Quickbooks\QuickbooksService; class ImportQuickbooksController extends BaseController { - // protected QuickbooksService $service; - private $import_entities = [ + private array $import_entities = [ 'client' => 'Customer', 'invoice' => 'Invoice', 'product' => 'Item', 'payment' => 'Payment' ]; - // public function __construct(QuickbooksService $service) { - // parent::__construct(); - - // $this->service = $service; - // $this->middleware( - // function (Request $request, Closure $next) { - - // // Check for the required query parameters - // if (!$request->has(['code', 'state', 'realmId'])) { - // return abort(400,'Unauthorized'); - // } - - // $rules = [ - // 'state' => [ - // 'required', - // 'valid' => function ($attribute, $value, $fail) { - // if (!Cache::has($value)) { - // $fail('The state is invalid.'); - // } - // }, - // ] - // ]; - // // Custom error messages - // $messages = [ - // 'state.required' => 'The state is required.', - // 'state.valid' => 'state token not valid' - // ]; - // // Perform the validation - // $validator = Validator::make($request->all(), $rules, $messages); - // if ($validator->fails()) { - // // If validation fails, redirect back with errors and input - // return redirect('/') - // ->withErrors($validator) - // ->withInput(); - // } - - // $token = Cache::pull($request->state); - // $request->merge(['company' => Cache::get($token) ]); - - // return $next($request); - // } - // )->only('onAuthorized'); - // $this->middleware( - // function ( Request $request, Closure $next) { - // $rules = [ - // 'token' => [ - // 'required', - // 'valid' => function ($attribute, $value, $fail) { - // if (!Cache::has($value) || (!Company::where('company_key', (Cache::get($value))['company_key'])->exists() )) { - // $fail('The company is invalid.'); - // } - // }, - // ] - // ]; - // // Custom error messages - // $messages = [ - // 'token.required' => 'The token is required.', - // 'token.valid' => 'Token note valid!' - // ]; - // // Perform the validation - // $validator = Validator::make(['token' => $request->token ], $rules, $messages); - // if ($validator->fails()) { - // return redirect() - // ->back() - // ->withErrors($validator) - // ->withInput(); - // } - - // //If validation passes, proceed to the next middleware/controller - // return $next($request); - // } - // )->only('authorizeQuickbooks'); - // } - - public function onAuthorized(Request $request) + public function onAuthorized(AuthorizedQuickbooksRequest $request) { - $realm = $request->query('realmId'); - $company_key = $request->input('company.company_key'); - $company_id = $request->input('company.id'); - $tokens = ($auth_service = $this->service->getOAuth())->accessToken($request->query('code'), $realm); - $auth_service->saveTokens($company_key, ['realm' => $realm] + $tokens); - return response(200); + MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']); + $company = $request->getCompany(); + $qb = new QuickbooksService($company); + + $realm = $request->query('realmId'); + $access_token_object = $qb->getAuth()->accessToken($request->query('code'), $realm); + nlog($access_token_object); + $company->quickbooks = $access_token_object; + $company->save(); + // $company_key = $request->input('company.company_key'); + // $company_id = $request->input('company.id'); + // $auth_service->saveTokens($company_key, ['realm' => $realm] + $tokens); + + return response()->json(['message' => 'Success'], 200); } /** diff --git a/app/Http/Requests/Quickbooks/AuthorizedQuickbooksRequest.php b/app/Http/Requests/Quickbooks/AuthorizedQuickbooksRequest.php new file mode 100644 index 000000000000..b7b2f3f5584e --- /dev/null +++ b/app/Http/Requests/Quickbooks/AuthorizedQuickbooksRequest.php @@ -0,0 +1,69 @@ +getTokenContent()); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules(): array + { + return [ + 'code' => 'required|string', + 'state' => 'required|string', + 'realmId' => 'required|string', + ]; + } + + /** + * Resolve one-time token instance. + * + * @return mixed + */ + public function getTokenContent() + { + $token = Cache::get($this->state); + + $data = Cache::get($token); + + return $data; + } + + public function getContact() + { + return User::findOrFail($this->getTokenContent()['user_id']); + } + + public function getCompany() + { + return Company::where('company_key', $this->getTokenContent()['company_key'])->firstOrFail(); + } +} diff --git a/app/Services/Import/Quickbooks/SdkWrapper.php b/app/Services/Import/Quickbooks/SdkWrapper.php index e14c86689f67..6e2d111b60b3 100644 --- a/app/Services/Import/Quickbooks/SdkWrapper.php +++ b/app/Services/Import/Quickbooks/SdkWrapper.php @@ -28,7 +28,7 @@ final class SdkWrapper implements QuickbooksInterface return ($this->sdk->getOAuth2LoginHelper())->getState(); } - public function getAccessToken() : array + public function getAccessToken() { return $this->getTokens(); } @@ -44,15 +44,17 @@ final class SdkWrapper implements QuickbooksInterface return $this->getTokens(); } - private function getTokens() : array { + private function getTokens() + { $token =($this->sdk->getOAuth2LoginHelper())->getAccessToken(); - $access_token = $token->getAccessToken(); - $refresh_token = $token->getRefreshToken(); - $access_token_expires = $token->getAccessTokenExpiresAt(); - $refresh_token_expires = $token->getRefreshTokenExpiresAt(); + 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'); + // return compact('access_token', 'refresh_token','access_token_expires', 'refresh_token_expires'); } public function refreshToken(): array From c894cdff8b73495a147b107c8566dec3756b7b36 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 21 Aug 2024 16:49:48 +1000 Subject: [PATCH 5/9] Refactor for quickbooks --- app/Http/Controllers/ImportQuickbooksController.php | 7 ++----- app/Models/Company.php | 1 + app/Services/Import/Quickbooks/Auth.php | 6 ------ app/Transformers/CompanyTransformer.php | 3 ++- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/app/Http/Controllers/ImportQuickbooksController.php b/app/Http/Controllers/ImportQuickbooksController.php index 558188c91aaf..7913f7a433a0 100644 --- a/app/Http/Controllers/ImportQuickbooksController.php +++ b/app/Http/Controllers/ImportQuickbooksController.php @@ -36,14 +36,11 @@ class ImportQuickbooksController extends BaseController $realm = $request->query('realmId'); $access_token_object = $qb->getAuth()->accessToken($request->query('code'), $realm); - nlog($access_token_object); + nlog($access_token_object); //OAuth2AccessToken $company->quickbooks = $access_token_object; $company->save(); - // $company_key = $request->input('company.company_key'); - // $company_id = $request->input('company.id'); - // $auth_service->saveTokens($company_key, ['realm' => $realm] + $tokens); - return response()->json(['message' => 'Success'], 200); + return response()->json(['message' => 'Success'], 200); //todo swapout for redirect to UI } /** diff --git a/app/Models/Company.php b/app/Models/Company.php index b1d504ca6943..bddb1e3c9619 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -374,6 +374,7 @@ class Company extends BaseModel 'ip', 'smtp_username', 'smtp_password', + 'quickbooks', ]; protected $casts = [ diff --git a/app/Services/Import/Quickbooks/Auth.php b/app/Services/Import/Quickbooks/Auth.php index ec0442fba4a2..edc3d605a8c0 100644 --- a/app/Services/Import/Quickbooks/Auth.php +++ b/app/Services/Import/Quickbooks/Auth.php @@ -39,12 +39,6 @@ final class Auth return $this->sdk->getState(); } - public function saveTokens($key, $tokens) - { - // $token_store = new CompanyTokensRepository($key); - // $token_store->save($tokens); - } - public function getAccessToken(): array { $tokens = []; diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index 71148d81f191..39617954fab2 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -212,7 +212,8 @@ class CompanyTransformer extends EntityTransformer 'smtp_local_domain' => (string)$company->smtp_local_domain ?? '', 'smtp_verify_peer' => (bool)$company->smtp_verify_peer, 'e_invoice' => $company->e_invoice ?: new \stdClass(), - 'quickbooks' => $company->quickbooks ?: new \stdClass(), + 'has_quickbooks_token' => $company->quickbooks ? true : false, + 'is_quickbooks_token_active' => $company->quickbooks?->accessTokenKey ?? false, ]; } From dfca75229da3e1360437fd1033eb1a22e41cbef0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 21 Aug 2024 17:19:44 +1000 Subject: [PATCH 6/9] Additional rules for credits --- app/Services/Bank/ProcessBankRules.php | 526 ++++++++++-------- ..._21_001832_add_einvoice_option_license.php | 4 + 2 files changed, 312 insertions(+), 218 deletions(-) diff --git a/app/Services/Bank/ProcessBankRules.php b/app/Services/Bank/ProcessBankRules.php index 171864304f1e..427baad9eeab 100644 --- a/app/Services/Bank/ProcessBankRules.php +++ b/app/Services/Bank/ProcessBankRules.php @@ -51,36 +51,32 @@ class ProcessBankRules extends AbstractService } } - // $payment.amount => "Payment Amount", float - // $payment.transaction_reference => "Payment Transaction Reference", string - // $invoice.amount => "Invoice Amount", float - // $invoice.number => "Invoice Number", string - // $client.id_number => "Client ID Number", string - // $client.email => "Client Email", string - // $invoice.po_number => "Invoice Purchase Order Number", string + // $payment.amount + // $payment.transaction_reference + // $payment.custom1 + // $payment.custom2 + // $payment.custom3 + // $payment.custom4 + // $invoice.amount + // $invoice.number + // $invoice.po_number + // $invoice.custom1 + // $invoice.custom2 + // $invoice.custom3 + // $invoice.custom4 + // $client.id_number + // $client.email + // $client.custom1 + // $client.custom2 + // $client.custom3 + // $client.custom4 private function matchCredit() { - $this->invoices = Invoice::query()->where('company_id', $this->bank_transaction->company_id) - ->whereIn('status_id', [1,2,3]) - ->where('is_deleted', 0) - ->get(); - - $invoice = $this->invoices->first(function ($value, $key) { - return str_contains($this->bank_transaction->description, $value->number) || str_contains(str_replace("\n", "", $this->bank_transaction->description), $value->number); - }); - - if ($invoice) { - $this->bank_transaction->invoice_ids = $invoice->hashed_id; - $this->bank_transaction->status_id = BankTransaction::STATUS_MATCHED; - $this->bank_transaction->save(); - return; - } - + $this->credit_rules = $this->bank_transaction->company->credit_rules(); - //stub for credit rules - foreach ($this->credit_rules as $bank_transaction_rule) { - $matches = 0; + foreach ($this->credit_rules as $bank_transaction_rule) + { if (!is_array($bank_transaction_rule['rules'])) { continue; @@ -89,284 +85,378 @@ class ProcessBankRules extends AbstractService foreach ($bank_transaction_rule['rules'] as $rule) { $rule_count = count($bank_transaction_rule['rules']); - $invoiceNumbers = false; - $invoiceNumber = false; - $invoiceAmounts = false; - $paymentAmounts = false; - $paymentReferences = false; - $clientIdNumbers = false; - $clientEmails = false; - $invoicePONumbers = false; + $invoices = Invoice::query() + ->withTrashed() + ->where('company_id', $this->bank_transaction->company_id) + ->whereIn('status_id', [1,2,3]) + ->where('is_deleted', 0) + ->get(); - if ($rule['search_key'] == '$invoice.number') { + $payments = Payment::query() + ->withTrashed() + ->whereIn('status_id', [1,4]) + ->where('is_deleted', 0) + ->whereNull('transaction_id') + ->get(); - $invoiceNumbers = Invoice::query()->where('company_id', $this->bank_transaction->company_id) - ->whereIn('status_id', [1,2,3]) - ->where('is_deleted', 0) - ->get(); + match($rule['search_key']){ + '$payment.amount' => $results = $this->searchPaymentResource('amount'), + '$payment.transaction_reference' => $results = $this->searchPaymentResource('transaction_reference'), + '$payment.custom1' => $results = $this->searchPaymentResource('custom1'), + '$payment.custom2' => $results = $this->searchPaymentResource('custom2'), + '$payment.custom3' => $results = $this->searchPaymentResource('custom3'), + '$payment.custom4' => $results = $this->searchPaymentResource('custom4'), + '$invoice.amount' => $results = $this->searchInvoiceResource('amount'), + '$invoice.number' => $results = $this->searchInvoiceResource('number'), + '$invoice.po_number' => $results = $this->searchInvoiceResource('po_number'), + '$invoice.custom1' => $results = $this->searchInvoiceResource('custom1'), + '$invoice.custom2' => $results = $this->searchInvoiceResource('custom2'), + '$invoice.custom3' => $results = $this->searchInvoiceResource('custom3'), + '$invoice.custom4' => $results = $this->searchInvoiceResource('custom4'), + '$client.id_number' => $results = $this->searchClientResource('id_number'), + '$client.email' => $results = $this->searchClientResource('email'), + '$client.custom1' => $results = $this->searchClientResource('custom1'), + '$client.custom2' => $results = $this->searchClientResource('custom2'), + '$client.custom3' => $results = $this->searchClientResource('custom3'), + '$client.custom4' => $results = $this->searchClientResource('custom4'), + }; + } - $invoiceNumber = $invoiceNumbers->first(function ($value, $key) { - return str_contains($this->bank_transaction->description, $value->number) || str_contains(str_replace("\n", "", $this->bank_transaction->description), $value->number); - }); + } + } - if($invoiceNumber) - $matches++; + private function searchInvoiceResource() + { - } + } + + private function searchPaymentResource() + { - if ($rule['search_key'] == '$invoice.po_number') { + } - $invoicePONumbers = Invoice::query()->where('company_id', $this->bank_transaction->company_id) - ->whereIn('status_id', [1,2,3]) - ->where('is_deleted', 0) - ->where('po_number', $this->bank_transaction->description) - ->get(); + private function searchClientResource() + { - if($invoicePONumbers->count() > 0) { - $matches++; - } - - } - - if ($rule['search_key'] == '$invoice.amount') { - - $$invoiceAmounts = Invoice::query()->where('company_id', $this->bank_transaction->company_id) - ->whereIn('status_id', [1,2,3]) - ->where('is_deleted', 0) - ->where('amount', $rule['operator'], $this->bank_transaction->amount) - ->get(); - - $invoiceAmounts = $this->invoices; - - if($invoiceAmounts->count() > 0) { - $matches++; - } - - } - - if ($rule['search_key'] == '$payment.amount') { + } + // $payment.amount => "Payment Amount", float + // $payment.transaction_reference => "Payment Transaction Reference", string + // $invoice.amount => "Invoice Amount", float + // $invoice.number => "Invoice Number", string + // $client.id_number => "Client ID Number", string + // $client.email => "Client Email", string + // $invoice.po_number => "Invoice Purchase Order Number", string - $paymentAmounts = Payment::query()->where('company_id', $this->bank_transaction->company_id) - ->whereIn('status_id', [1,4]) - ->where('is_deleted', 0) - ->whereNull('transaction_id') - ->where('amount', $rule['operator'], $this->bank_transaction->amount) - ->get(); + // private function matchCredit() + // { + // $this->invoices = Invoice::query()->where('company_id', $this->bank_transaction->company_id) + // ->whereIn('status_id', [1,2,3]) + // ->where('is_deleted', 0) + // ->get(); + + // $invoice = $this->invoices->first(function ($value, $key) { + // return str_contains($this->bank_transaction->description, $value->number) || str_contains(str_replace("\n", "", $this->bank_transaction->description), $value->number); + // }); + + // if ($invoice) { + // $this->bank_transaction->invoice_ids = $invoice->hashed_id; + // $this->bank_transaction->status_id = BankTransaction::STATUS_MATCHED; + // $this->bank_transaction->save(); + // return; + // } + + // $this->credit_rules = $this->bank_transaction->company->credit_rules(); + + // //stub for credit rules + // foreach ($this->credit_rules as $bank_transaction_rule) { + // $matches = 0; + + // if (!is_array($bank_transaction_rule['rules'])) { + // continue; + // } + + // foreach ($bank_transaction_rule['rules'] as $rule) { + // $rule_count = count($bank_transaction_rule['rules']); + + // $invoiceNumbers = false; + // $invoiceNumber = false; + // $invoiceAmounts = false; + // $paymentAmounts = false; + // $paymentReferences = false; + // $clientIdNumbers = false; + // $clientEmails = false; + // $invoicePONumbers = false; + + // if ($rule['search_key'] == '$invoice.number') { + + // $invoiceNumbers = Invoice::query()->where('company_id', $this->bank_transaction->company_id) + // ->whereIn('status_id', [1,2,3]) + // ->where('is_deleted', 0) + // ->get(); + + // $invoiceNumber = $invoiceNumbers->first(function ($value, $key) { + // return str_contains($this->bank_transaction->description, $value->number) || str_contains(str_replace("\n", "", $this->bank_transaction->description), $value->number); + // }); + + // if($invoiceNumber) + // $matches++; + + // } + + // if ($rule['search_key'] == '$invoice.po_number') { + + // $invoicePONumbers = Invoice::query()->where('company_id', $this->bank_transaction->company_id) + // ->whereIn('status_id', [1,2,3]) + // ->where('is_deleted', 0) + // ->where('po_number', $this->bank_transaction->description) + // ->get(); + + // if($invoicePONumbers->count() > 0) { + // $matches++; + // } + + // } + + // if ($rule['search_key'] == '$invoice.amount') { + + // $$invoiceAmounts = Invoice::query()->where('company_id', $this->bank_transaction->company_id) + // ->whereIn('status_id', [1,2,3]) + // ->where('is_deleted', 0) + // ->where('amount', $rule['operator'], $this->bank_transaction->amount) + // ->get(); + + // $invoiceAmounts = $this->invoices; + + // if($invoiceAmounts->count() > 0) { + // $matches++; + // } + + // } + + // if ($rule['search_key'] == '$payment.amount') { + + + // $paymentAmounts = Payment::query()->where('company_id', $this->bank_transaction->company_id) + // ->whereIn('status_id', [1,4]) + // ->where('is_deleted', 0) + // ->whereNull('transaction_id') + // ->where('amount', $rule['operator'], $this->bank_transaction->amount) + // ->get(); - if($paymentAmounts->count() > 0) { - $matches++; - } + // if($paymentAmounts->count() > 0) { + // $matches++; + // } - } + // } - if ($rule['search_key'] == '$payment.transaction_reference') { + // if ($rule['search_key'] == '$payment.transaction_reference') { - $ref_search = $this->bank_transaction->description; + // $ref_search = $this->bank_transaction->description; - switch ($rule['operator']) { - case 'is': - $operator = '='; - break; - case 'contains': - $ref_search = "%".$ref_search."%"; - $operator = 'LIKE'; - break; + // switch ($rule['operator']) { + // case 'is': + // $operator = '='; + // break; + // case 'contains': + // $ref_search = "%".$ref_search."%"; + // $operator = 'LIKE'; + // break; - default: - $operator = '='; - break; - } + // default: + // $operator = '='; + // break; + // } - $paymentReferences = Payment::query()->where('company_id', $this->bank_transaction->company_id) - ->whereIn('status_id', [1,4]) - ->where('is_deleted', 0) - ->whereNull('transaction_id') - ->where('transaction_reference', $operator, $ref_search) - ->get(); + // $paymentReferences = Payment::query()->where('company_id', $this->bank_transaction->company_id) + // ->whereIn('status_id', [1,4]) + // ->where('is_deleted', 0) + // ->whereNull('transaction_id') + // ->where('transaction_reference', $operator, $ref_search) + // ->get(); - if($paymentReferences->count() > 0) { - $matches++; - } + // if($paymentReferences->count() > 0) { + // $matches++; + // } - } + // } - if ($rule['search_key'] == '$client.id_number') { + // if ($rule['search_key'] == '$client.id_number') { - $ref_search = $this->bank_transaction->description; + // $ref_search = $this->bank_transaction->description; - switch ($rule['operator']) { - case 'is': - $operator = '='; - break; - case 'contains': - $ref_search = "%".$ref_search."%"; - $operator = 'LIKE'; - break; + // switch ($rule['operator']) { + // case 'is': + // $operator = '='; + // break; + // case 'contains': + // $ref_search = "%".$ref_search."%"; + // $operator = 'LIKE'; + // break; - default: - $operator = '='; - break; - } + // default: + // $operator = '='; + // break; + // } - $clientIdNumbers = Client::query()->where('company_id', $this->bank_transaction->company_id) - ->where('id_number', $operator, $ref_search) - ->get(); + // $clientIdNumbers = Client::query()->where('company_id', $this->bank_transaction->company_id) + // ->where('id_number', $operator, $ref_search) + // ->get(); - if($clientIdNumbers->count() > 0) { - $matches++; - } + // if($clientIdNumbers->count() > 0) { + // $matches++; + // } - } + // } - if ($rule['search_key'] == '$client.email') { + // if ($rule['search_key'] == '$client.email') { - $clientEmails = Client::query() - ->where('company_id', $this->bank_transaction->company_id) - ->whereHas('contacts', function ($q){ - $q->where('email', $this->bank_transaction->description); - }) - ->get(); + // $clientEmails = Client::query() + // ->where('company_id', $this->bank_transaction->company_id) + // ->whereHas('contacts', function ($q){ + // $q->where('email', $this->bank_transaction->description); + // }) + // ->get(); - if($clientEmails->count() > 0) { - $matches++; - } + // if($clientEmails->count() > 0) { + // $matches++; + // } - if (($bank_transaction_rule['matches_on_all'] && ($matches == $rule_count)) || (!$bank_transaction_rule['matches_on_all'] && $matches > 0)) { + // if (($bank_transaction_rule['matches_on_all'] && ($matches == $rule_count)) || (!$bank_transaction_rule['matches_on_all'] && $matches > 0)) { - //determine which combination has succeeded, ie link a payment / or / invoice - $invoice_ids = null; - $payment_id = null; + // //determine which combination has succeeded, ie link a payment / or / invoice + // $invoice_ids = null; + // $payment_id = null; - if($invoiceNumber){ - $invoice_ids = $invoiceNumber->hashed_id; - } + // if($invoiceNumber){ + // $invoice_ids = $invoiceNumber->hashed_id; + // } - if($invoicePONumbers && strlen($invoice_ids ?? '') == 0){ + // if($invoicePONumbers && strlen($invoice_ids ?? '') == 0){ - if($clientEmails){ // @phpstan-ignore-line + // if($clientEmails){ // @phpstan-ignore-line - $invoice_ids = $this->matchInvoiceAndClient($invoicePONumbers, $clientEmails); + // $invoice_ids = $this->matchInvoiceAndClient($invoicePONumbers, $clientEmails); - } + // } - if($clientIdNumbers && strlen($invoice_ids ?? '') == 0) - { + // if($clientIdNumbers && strlen($invoice_ids ?? '') == 0) + // { - $invoice_ids = $this->matchInvoiceAndClient($invoicePONumbers, $clientIdNumbers); + // $invoice_ids = $this->matchInvoiceAndClient($invoicePONumbers, $clientIdNumbers); - } + // } - if(strlen($invoice_ids ?? '') == 0) - { - $invoice_ids = $invoicePONumbers->first()->hashed_id; - } + // if(strlen($invoice_ids ?? '') == 0) + // { + // $invoice_ids = $invoicePONumbers->first()->hashed_id; + // } - } + // } - if($invoiceAmounts && strlen($invoice_ids ?? '') == 0) { + // if($invoiceAmounts && strlen($invoice_ids ?? '') == 0) { - if($clientEmails) {// @phpstan-ignore-line + // if($clientEmails) {// @phpstan-ignore-line - $invoice_ids = $this->matchInvoiceAndClient($invoiceAmounts, $clientEmails); + // $invoice_ids = $this->matchInvoiceAndClient($invoiceAmounts, $clientEmails); - } + // } - if($clientIdNumbers && strlen($invoice_ids ?? '') == 0) { + // if($clientIdNumbers && strlen($invoice_ids ?? '') == 0) { - $invoice_ids = $this->matchInvoiceAndClient($invoiceAmounts, $clientIdNumbers); + // $invoice_ids = $this->matchInvoiceAndClient($invoiceAmounts, $clientIdNumbers); - } + // } - if(strlen($invoice_ids ?? '') == 0) { - $invoice_ids = $invoiceAmounts->first()->hashed_id; - } + // if(strlen($invoice_ids ?? '') == 0) { + // $invoice_ids = $invoiceAmounts->first()->hashed_id; + // } - } + // } - if($paymentAmounts && strlen($invoice_ids ?? '') == 0 && is_null($payment_id)) { + // if($paymentAmounts && strlen($invoice_ids ?? '') == 0 && is_null($payment_id)) { - if($clientEmails) {// @phpstan-ignore-line + // if($clientEmails) {// @phpstan-ignore-line - $payment_id = $this->matchPaymentAndClient($paymentAmounts, $clientEmails); + // $payment_id = $this->matchPaymentAndClient($paymentAmounts, $clientEmails); - } + // } - if($clientIdNumbers && is_null($payment_id)) { + // if($clientIdNumbers && is_null($payment_id)) { - $payment_id = $this->matchPaymentAndClient($paymentAmounts, $clientEmails); + // $payment_id = $this->matchPaymentAndClient($paymentAmounts, $clientEmails); - } + // } - if(is_null($payment_id)) { - $payment_id = $paymentAmounts->first()->id; - } + // if(is_null($payment_id)) { + // $payment_id = $paymentAmounts->first()->id; + // } - } + // } - if(strlen($invoice_ids ?? '') > 1 || is_int($payment_id)) - { + // if(strlen($invoice_ids ?? '') > 1 || is_int($payment_id)) + // { - $this->bank_transaction->payment_id = $payment_id; - $this->bank_transaction->invoice_ids = $invoice_ids; - $this->bank_transaction->status_id = BankTransaction::STATUS_MATCHED; - $this->bank_transaction->bank_transaction_rule_id = $bank_transaction_rule->id; - $this->bank_transaction->save(); + // $this->bank_transaction->payment_id = $payment_id; + // $this->bank_transaction->invoice_ids = $invoice_ids; + // $this->bank_transaction->status_id = BankTransaction::STATUS_MATCHED; + // $this->bank_transaction->bank_transaction_rule_id = $bank_transaction_rule->id; + // $this->bank_transaction->save(); - } + // } - } + // } - } + // } - } + // } - } + // } - } + // } - private function matchPaymentAndClient($payments, $clients): ?int - { - /** @var \Illuminate\Support\Collection $payments */ - foreach($payments as $payment) { - foreach($clients as $client) { + // private function matchPaymentAndClient($payments, $clients): ?int + // { + // /** @var \Illuminate\Support\Collection $payments */ + // foreach($payments as $payment) { + // foreach($clients as $client) { - if($payment->client_id == $client->id) { - return $payment->id; + // if($payment->client_id == $client->id) { + // return $payment->id; - } - } - } + // } + // } + // } - return null; - } + // return null; + // } - private function matchInvoiceAndClient($invoices, $clients): ?Invoice - { - /** @var \Illuminate\Support\Collection $invoices */ - foreach($invoices as $invoice) { - foreach($clients as $client) { + // private function matchInvoiceAndClient($invoices, $clients): ?Invoice + // { + // /** @var \Illuminate\Support\Collection $invoices */ + // foreach($invoices as $invoice) { + // foreach($clients as $client) { - if($invoice->client_id == $client->id) { - return $invoice->hashed_id; + // if($invoice->client_id == $client->id) { + // return $invoice->hashed_id; - } - } - } + // } + // } + // } - return null; - } + // return null; + // } private function matchDebit() { diff --git a/database/migrations/2024_08_21_001832_add_einvoice_option_license.php b/database/migrations/2024_08_21_001832_add_einvoice_option_license.php index c7228383a54c..7ab441f6840a 100644 --- a/database/migrations/2024_08_21_001832_add_einvoice_option_license.php +++ b/database/migrations/2024_08_21_001832_add_einvoice_option_license.php @@ -16,6 +16,10 @@ return new class extends Migration $table->unsignedInteger('e_invoice_quota')->nullable()->index(); }); + Schema::table('bank_transaction_rules', function (Blueprint $table){ + $table->enum('on_credit_match', ['create_payment', 'link_payment'])->default('create_payment'); + }); + } /** From e63c50f58f690851af4a437fe66af35c07e8884c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 21 Aug 2024 17:46:07 +1000 Subject: [PATCH 7/9] Refactors for Bank Transactions --- .../StoreBankTransactionRuleRequest.php | 3 +- app/Services/Bank/ProcessBankRules.php | 62 +++++++++++-------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php b/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php index 28877fa13dbd..ebbca33b4337 100644 --- a/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php +++ b/app/Http/Requests/BankTransactionRule/StoreBankTransactionRuleRequest.php @@ -48,7 +48,8 @@ class StoreBankTransactionRuleRequest extends Request 'rules.*.value' => 'bail|required|nullable', 'auto_convert' => 'bail|sometimes|bool', 'matches_on_all' => 'bail|sometimes|bool', - 'applies_to' => 'bail|sometimes|string', + 'applies_to' => 'bail|sometimes|string|in:CREDIT,DEBIT', + 'on_credit_match' => 'bail|sometimes|in:create_payment,link_payment' ]; $rules['category_id'] = 'bail|sometimes|nullable|exists:expense_categories,id,company_id,'.$user->company()->id.',is_deleted,0'; diff --git a/app/Services/Bank/ProcessBankRules.php b/app/Services/Bank/ProcessBankRules.php index 427baad9eeab..4710b2cd08fc 100644 --- a/app/Services/Bank/ProcessBankRules.php +++ b/app/Services/Bank/ProcessBankRules.php @@ -85,12 +85,6 @@ class ProcessBankRules extends AbstractService foreach ($bank_transaction_rule['rules'] as $rule) { $rule_count = count($bank_transaction_rule['rules']); - $invoices = Invoice::query() - ->withTrashed() - ->where('company_id', $this->bank_transaction->company_id) - ->whereIn('status_id', [1,2,3]) - ->where('is_deleted', 0) - ->get(); $payments = Payment::query() ->withTrashed() @@ -100,34 +94,50 @@ class ProcessBankRules extends AbstractService ->get(); match($rule['search_key']){ - '$payment.amount' => $results = $this->searchPaymentResource('amount'), - '$payment.transaction_reference' => $results = $this->searchPaymentResource('transaction_reference'), - '$payment.custom1' => $results = $this->searchPaymentResource('custom1'), - '$payment.custom2' => $results = $this->searchPaymentResource('custom2'), - '$payment.custom3' => $results = $this->searchPaymentResource('custom3'), - '$payment.custom4' => $results = $this->searchPaymentResource('custom4'), - '$invoice.amount' => $results = $this->searchInvoiceResource('amount'), - '$invoice.number' => $results = $this->searchInvoiceResource('number'), - '$invoice.po_number' => $results = $this->searchInvoiceResource('po_number'), - '$invoice.custom1' => $results = $this->searchInvoiceResource('custom1'), - '$invoice.custom2' => $results = $this->searchInvoiceResource('custom2'), - '$invoice.custom3' => $results = $this->searchInvoiceResource('custom3'), - '$invoice.custom4' => $results = $this->searchInvoiceResource('custom4'), - '$client.id_number' => $results = $this->searchClientResource('id_number'), - '$client.email' => $results = $this->searchClientResource('email'), - '$client.custom1' => $results = $this->searchClientResource('custom1'), - '$client.custom2' => $results = $this->searchClientResource('custom2'), - '$client.custom3' => $results = $this->searchClientResource('custom3'), - '$client.custom4' => $results = $this->searchClientResource('custom4'), + '$payment.amount' => $results = $this->searchPaymentResource('amount', $rule), + '$payment.transaction_reference' => $results = $this->searchPaymentResource('transaction_reference', $rule), + '$payment.custom1' => $results = $this->searchPaymentResource('custom1', $rule), + '$payment.custom2' => $results = $this->searchPaymentResource('custom2', $rule), + '$payment.custom3' => $results = $this->searchPaymentResource('custom3', $rule), + '$payment.custom4' => $results = $this->searchPaymentResource('custom4', $rule), + '$invoice.amount' => $results = $this->searchInvoiceResource('amount', $rule), + '$invoice.number' => $results = $this->searchInvoiceResource('number', $rule), + '$invoice.po_number' => $results = $this->searchInvoiceResource('po_number', $rule), + '$invoice.custom1' => $results = $this->searchInvoiceResource('custom1', $rule), + '$invoice.custom2' => $results = $this->searchInvoiceResource('custom2', $rule), + '$invoice.custom3' => $results = $this->searchInvoiceResource('custom3', $rule), + '$invoice.custom4' => $results = $this->searchInvoiceResource('custom4', $rule), + '$client.id_number' => $results = $this->searchClientResource('id_number', $rule), + '$client.email' => $results = $this->searchClientResource('email', $rule), + '$client.custom1' => $results = $this->searchClientResource('custom1', $rule), + '$client.custom2' => $results = $this->searchClientResource('custom2', $rule), + '$client.custom3' => $results = $this->searchClientResource('custom3', $rule), + '$client.custom4' => $results = $this->searchClientResource('custom4', $rule), }; } } } - private function searchInvoiceResource() + private function searchInvoiceResource(string $column, array $rule) { + return Invoice::query() + ->withTrashed() + ->where('company_id', $this->bank_transaction->company_id) + ->whereIn('status_id', [1,2,3]) + ->where('is_deleted', 0) + ->when($rule['search_key'] == 'description', function ($q) use ($rule, $column){ + return $q->cursor()->filter(function ($record) use ($rule, $column){ + return $this->matchStringOperator($this->bank_transaction->description, $record->{$column}, $rule['operator']); + }); + }) + ->when($rule['search_key'] == 'amount', function ($q) use($rule,$column){ + return $q->cursor()->filter(function ($record) use ($rule, $column) { + return $this->matchNumberOperator($this->bank_transaction->amount, $record->{$column}, $rule['operator']); + }); + })->pluck("id"); + } private function searchPaymentResource() From fd6d60327b58b217415022c652541a13be55d01f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 22 Aug 2024 07:45:07 +1000 Subject: [PATCH 8/9] Rules for DE --- app/DataMapper/Tax/DE/Rule.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/DataMapper/Tax/DE/Rule.php b/app/DataMapper/Tax/DE/Rule.php index 34d63bffefa9..a1b4cb356640 100644 --- a/app/DataMapper/Tax/DE/Rule.php +++ b/app/DataMapper/Tax/DE/Rule.php @@ -241,9 +241,8 @@ class Rule extends BaseRule implements RuleInterface // nlog("tax exempt"); $this->tax_rate = 0; $this->reduced_tax_rate = 0; - } elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->vat_number && $this->eu_business_tax_exempt) { - // elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->has_valid_vat_number && $this->eu_business_tax_exempt) - // nlog("euro zone and tax exempt"); + } elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->vat_number && $this->client->has_valid_vat_number && $this->eu_business_tax_exempt) { + // nlog("euro zone and tax exempt"); $this->tax_rate = 0; $this->reduced_tax_rate = 0; } elseif(!in_array($this->client_subregion, $this->eu_country_codes) && ($this->foreign_consumer_tax_exempt || $this->foreign_business_tax_exempt)) { //foreign + tax exempt @@ -252,8 +251,9 @@ class Rule extends BaseRule implements RuleInterface $this->reduced_tax_rate = 0; } elseif(!in_array($this->client_subregion, $this->eu_country_codes)) { $this->defaultForeign(); - } elseif(in_array($this->client_subregion, $this->eu_country_codes) && !$this->client->vat_number) { //eu country / no valid vat - if(($this->client->company->tax_data->seller_subregion != $this->client_subregion) && $this->client->company->tax_data->regions->EU->has_sales_above_threshold) { + } elseif(in_array($this->client_subregion, $this->eu_country_codes) && ((strlen($this->client->vat_number ?? '') == 1) || $this->client->has_valid_vat_number)) { //eu country / no valid vat + // if(($this->client->company->tax_data->seller_subregion != $this->client_subregion) && $this->client->company->tax_data->regions->EU->has_sales_above_threshold) { + if($this->client->company->tax_data->seller_subregion != $this->client_subregion) { // nlog("eu zone with sales above threshold"); $this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->tax_rate ?? 0; $this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->reduced_tax_rate ?? 0; From f258bdb78e75e3944c334368599001eefe2a04f5 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 22 Aug 2024 08:14:54 +1000 Subject: [PATCH 9/9] Typo --- app/Services/Import/Quickbooks/SdkWrapper.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Services/Import/Quickbooks/SdkWrapper.php b/app/Services/Import/Quickbooks/SdkWrapper.php index 6e2d111b60b3..8c73e44a5167 100644 --- a/app/Services/Import/Quickbooks/SdkWrapper.php +++ b/app/Services/Import/Quickbooks/SdkWrapper.php @@ -48,7 +48,8 @@ final class SdkWrapper implements QuickbooksInterface { $token =($this->sdk->getOAuth2LoginHelper())->getAccessToken(); - return $token + return $token; + // $access_token = $token->getAccessToken(); // $refresh_token = $token->getRefreshToken(); // $access_token_expires = $token->getAccessTokenExpiresAt();