mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge pull request #9934 from turbo124/v5-develop
Fixes for inter EU tax rules
This commit is contained in:
commit
6607b23046
@ -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;
|
||||
|
@ -1,63 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Factory;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use QuickBooksOnline\API\DataService\DataService;
|
||||
use App\Services\Import\Quickbooks\Repositories\CompanyTokensRepository;
|
||||
|
||||
|
||||
class QuickbooksSDKFactory
|
||||
{
|
||||
public static function create()
|
||||
{
|
||||
$tokens = [];
|
||||
// Ensure the user is authenticated
|
||||
if(($user = Auth::user()))
|
||||
{
|
||||
$company = $user->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;
|
||||
}
|
||||
}
|
@ -2,111 +2,45 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\Quickbooks\AuthorizedQuickbooksRequest;
|
||||
use \Closure;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Company;
|
||||
use App\Libraries\MultiDB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use 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;
|
||||
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); //OAuth2AccessToken
|
||||
$company->quickbooks = $access_token_object;
|
||||
$company->save();
|
||||
|
||||
return response()->json(['message' => 'Success'], 200); //todo swapout for redirect to UI
|
||||
}
|
||||
|
||||
/**
|
||||
@ -114,12 +48,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 +123,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() );
|
||||
|
@ -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';
|
||||
|
69
app/Http/Requests/Quickbooks/AuthQuickbooksRequest.php
Normal file
69
app/Http/Requests/Quickbooks/AuthQuickbooksRequest.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?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\Requests\Quickbooks;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class AuthQuickbooksRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return is_array($this->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();
|
||||
}
|
||||
}
|
69
app/Http/Requests/Quickbooks/AuthorizedQuickbooksRequest.php
Normal file
69
app/Http/Requests/Quickbooks/AuthorizedQuickbooksRequest.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?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\Requests\Quickbooks;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class AuthorizedQuickbooksRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return is_array($this->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();
|
||||
}
|
||||
}
|
@ -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<int, \App\Models\Activity> $activities
|
||||
@ -373,6 +374,7 @@ class Company extends BaseModel
|
||||
'ip',
|
||||
'smtp_username',
|
||||
'smtp_password',
|
||||
'quickbooks',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@ -390,6 +392,7 @@ class Company extends BaseModel
|
||||
'smtp_username' => 'encrypted',
|
||||
'smtp_password' => 'encrypted',
|
||||
'e_invoice' => 'object',
|
||||
'quickbooks' => 'object',
|
||||
];
|
||||
|
||||
protected $with = [];
|
||||
|
@ -1,84 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Factory\QuickbooksSDKFactory;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use App\Http\Controllers\ImportQuickbooksController;
|
||||
use App\Services\Import\Quickbooks\Service as QuickbooksService;
|
||||
use App\Repositories\Import\Quickcbooks\Contracts\RepositoryInterface;
|
||||
use App\Services\Import\Quickbooks\SdkWrapper as QuickbooksSDKWrapper;
|
||||
use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface;
|
||||
use App\Services\Import\Quickbooks\Transformers\Transformer as QuickbooksTransformer;
|
||||
|
||||
class QuickbooksServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
|
||||
$this->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');
|
||||
});
|
||||
}
|
||||
}
|
@ -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,388 @@ 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;
|
||||
|
||||
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', $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),
|
||||
};
|
||||
}
|
||||
|
||||
$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(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()
|
||||
{
|
||||
|
||||
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<Payment> $payments */
|
||||
foreach($payments as $payment) {
|
||||
foreach($clients as $client) {
|
||||
// private function matchPaymentAndClient($payments, $clients): ?int
|
||||
// {
|
||||
// /** @var \Illuminate\Support\Collection<Payment> $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<Invoice> $invoices */
|
||||
foreach($invoices as $invoice) {
|
||||
foreach($clients as $client) {
|
||||
// private function matchInvoiceAndClient($invoices, $clients): ?Invoice
|
||||
// {
|
||||
// /** @var \Illuminate\Support\Collection<Invoice> $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()
|
||||
{
|
||||
|
@ -1,66 +1,64 @@
|
||||
<?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;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Services\Import\Quickbooks\Repositories\CompanyTokensRepository;
|
||||
use App\Services\Import\QuickBooks\Contracts\SDKInterface as QuickbooksInterface;
|
||||
|
||||
final class Auth
|
||||
{
|
||||
private QuickbooksInterface $sdk;
|
||||
|
||||
public function __construct(QuickbooksInterface $quickbooks) {
|
||||
$this->sdk = $quickbooks;
|
||||
{
|
||||
public function __construct(private SdkWrapper $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)
|
||||
public function getAccessToken(): array
|
||||
{
|
||||
$token_store = new CompanyTokensRepository($key);
|
||||
$token_store->save($tokens);
|
||||
}
|
||||
$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');
|
||||
// }
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
public function getRefreshToken() : array
|
||||
public function getRefreshToken(): array
|
||||
{
|
||||
return $this->getAccessToken();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
72
app/Services/Import/Quickbooks/QuickbooksService.php
Normal file
72
app/Services/Import/Quickbooks/QuickbooksService.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?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;
|
||||
|
||||
use App\Models\Company;
|
||||
use QuickBooksOnline\API\DataService\DataService;
|
||||
|
||||
// quickbooks_realm_id
|
||||
// quickbooks_refresh_token
|
||||
// quickbooks_refresh_expires
|
||||
class QuickbooksService
|
||||
{
|
||||
private DataService $sdk;
|
||||
|
||||
private Auth $auth;
|
||||
|
||||
public function __construct(private Company $company)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
@ -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,18 @@ 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
|
||||
|
@ -212,6 +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(),
|
||||
'has_quickbooks_token' => $company->quickbooks ? true : false,
|
||||
'is_quickbooks_token_active' => $company->quickbooks?->accessTokenKey ?? false,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -201,7 +201,6 @@ return [
|
||||
App\Providers\ClientPortalServiceProvider::class,
|
||||
App\Providers\NinjaTranslationServiceProvider::class,
|
||||
App\Providers\StaticServiceProvider::class,
|
||||
App\Providers\QuickbooksServiceProvider::class
|
||||
],
|
||||
|
||||
/*
|
||||
|
@ -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)
|
||||
],
|
||||
];
|
||||
|
@ -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']);
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
@ -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');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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');
|
||||
|
@ -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']]);
|
||||
|
@ -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() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user