mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-11-03 21:37:34 -05:00 
			
		
		
		
	Merge pull request #9917 from M-E-Development-Design/feature/import-quickbooks
Feature/import quickbooks
This commit is contained in:
		
						commit
						37822d7a0d
					
				
							
								
								
									
										63
									
								
								app/Factory/QuickbooksSDKFactory.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								app/Factory/QuickbooksSDKFactory.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
<?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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										199
									
								
								app/Http/Controllers/ImportQuickbooksController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								app/Http/Controllers/ImportQuickbooksController.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,199 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Http\Controllers;
 | 
			
		||||
 | 
			
		||||
use \Closure;
 | 
			
		||||
use App\Utils\Ninja;
 | 
			
		||||
use App\Models\Company;
 | 
			
		||||
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\Validator;
 | 
			
		||||
use App\Services\Import\Quickbooks\Service as QuickbooksService;
 | 
			
		||||
 | 
			
		||||
class ImportQuickbooksController extends BaseController
 | 
			
		||||
{
 | 
			
		||||
    protected QuickbooksService $service; 
 | 
			
		||||
    private $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)
 | 
			
		||||
    {
 | 
			
		||||
        $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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determine if the user is authorized to make this request.
 | 
			
		||||
     *
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    public function authorizeQuickbooks(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        $token = $request->token;
 | 
			
		||||
        $auth = $this->service->getOAuth();
 | 
			
		||||
        $authorizationUrl = $auth->getAuthorizationUrl();
 | 
			
		||||
        $state = $auth->getState();
 | 
			
		||||
 | 
			
		||||
        Cache::put($state, $token, 190);
 | 
			
		||||
 | 
			
		||||
        return redirect()->to($authorizationUrl);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function preimport(string $type, string $hash)
 | 
			
		||||
    {
 | 
			
		||||
        // Check for authorization otherwise 
 | 
			
		||||
        // Create a reference
 | 
			
		||||
        $data = [
 | 
			
		||||
            'hash' => $hash,
 | 
			
		||||
            'type' => $type
 | 
			
		||||
        ];
 | 
			
		||||
        $this->getData($data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function getData($data) {
 | 
			
		||||
 | 
			
		||||
        $entity = $this->import_entities[$data['type']];
 | 
			
		||||
        $cache_name = "{$data['hash']}-{$data['type']}";
 | 
			
		||||
        // TODO: Get or put cache  or DB?
 | 
			
		||||
        if(! Cache::has($cache_name) )
 | 
			
		||||
        {
 | 
			
		||||
            $contents = call_user_func([$this->service, "fetch{$entity}s"]);
 | 
			
		||||
            if($contents->isEmpty()) return;
 | 
			
		||||
            
 | 
			
		||||
            Cache::put($cache_name, base64_encode( $contents->toJson()), 600);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @OA\Post(
 | 
			
		||||
     *      path="/api/v1/import_json",
 | 
			
		||||
     *      operationId="getImportJson",
 | 
			
		||||
     *      tags={"import"},
 | 
			
		||||
     *      summary="Import data from the system",
 | 
			
		||||
     *      description="Import data from the system",
 | 
			
		||||
     *      @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
 | 
			
		||||
     *      @OA\Response(
 | 
			
		||||
     *          response=200,
 | 
			
		||||
     *          description="success",
 | 
			
		||||
     *          @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
 | 
			
		||||
     *          @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
 | 
			
		||||
     *          @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
 | 
			
		||||
     *       ),
 | 
			
		||||
     *       @OA\Response(
 | 
			
		||||
     *          response=422,
 | 
			
		||||
     *          description="Validation error",
 | 
			
		||||
     *          @OA\JsonContent(ref="#/components/schemas/ValidationError"),
 | 
			
		||||
     *       ),
 | 
			
		||||
     *       @OA\Response(
 | 
			
		||||
     *           response="default",
 | 
			
		||||
     *           description="Unexpected Error",
 | 
			
		||||
     *           @OA\JsonContent(ref="#/components/schemas/Error"),
 | 
			
		||||
     *       ),
 | 
			
		||||
     *     )
 | 
			
		||||
     */
 | 
			
		||||
    public function import(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
         $hash = Str::random(32);
 | 
			
		||||
        foreach($request->input('import_types') as $type)
 | 
			
		||||
        {
 | 
			
		||||
            $this->preimport($type, $hash);
 | 
			
		||||
        }
 | 
			
		||||
        /** @var \App\Models\User $user */
 | 
			
		||||
        $user = auth()->user() ?? Auth::loginUsingId(60);
 | 
			
		||||
        $data = ['import_types' => $request->input('import_types') ] + compact('hash');
 | 
			
		||||
        if (Ninja::isHosted()) {
 | 
			
		||||
            QuickbooksIngest::dispatch( $data , $user->company() );
 | 
			
		||||
        } else {
 | 
			
		||||
            QuickbooksIngest::dispatch($data, $user->company() );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return response()->json(['message' => 'Processing'], 200);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										252
									
								
								app/Import/Providers/Quickbooks.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								app/Import/Providers/Quickbooks.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,252 @@
 | 
			
		||||
<?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\Import\Providers;
 | 
			
		||||
 | 
			
		||||
use App\Models\Invoice;
 | 
			
		||||
use App\Factory\ProductFactory;
 | 
			
		||||
use App\Factory\ClientFactory;
 | 
			
		||||
use App\Factory\InvoiceFactory;
 | 
			
		||||
use App\Factory\PaymentFactory;
 | 
			
		||||
use Illuminate\Support\Facades\Cache;
 | 
			
		||||
use App\Repositories\ClientRepository;
 | 
			
		||||
use App\Repositories\InvoiceRepository;
 | 
			
		||||
use App\Repositories\ProductRepository;
 | 
			
		||||
use App\Repositories\PaymentRepository;
 | 
			
		||||
use App\Http\Requests\Client\StoreClientRequest;
 | 
			
		||||
use App\Http\Requests\Product\StoreProductRequest;
 | 
			
		||||
use App\Http\Requests\Invoice\StoreInvoiceRequest;
 | 
			
		||||
use App\Http\Requests\Payment\StorePaymentRequest;
 | 
			
		||||
use App\Import\Transformer\Quickbooks\ClientTransformer;
 | 
			
		||||
use App\Import\Transformer\Quickbooks\InvoiceTransformer;
 | 
			
		||||
use App\Import\Transformer\Quickbooks\ProductTransformer;
 | 
			
		||||
use App\Import\Transformer\Quickbooks\PaymentTransformer;
 | 
			
		||||
 | 
			
		||||
class Quickbooks extends BaseImport
 | 
			
		||||
{
 | 
			
		||||
    public array $entity_count = [];
 | 
			
		||||
 | 
			
		||||
    public function import(string $entity)
 | 
			
		||||
    {
 | 
			
		||||
        if (
 | 
			
		||||
            in_array($entity, [
 | 
			
		||||
                'client',
 | 
			
		||||
                'invoice',
 | 
			
		||||
                'product',
 | 
			
		||||
                'payment',
 | 
			
		||||
                // 'vendor',
 | 
			
		||||
                // 'expense',
 | 
			
		||||
            ])
 | 
			
		||||
        ) {
 | 
			
		||||
            $this->{$entity}();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //collate any errors
 | 
			
		||||
 | 
			
		||||
        // $this->finalizeImport();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function client()
 | 
			
		||||
    {
 | 
			
		||||
        $entity_type = 'client';
 | 
			
		||||
        $data = $this->getData($entity_type);
 | 
			
		||||
        if (empty($data)) {
 | 
			
		||||
            $this->entity_count['clients'] = 0;
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->request_name = StoreClientRequest::class;
 | 
			
		||||
        $this->repository_name = ClientRepository::class;
 | 
			
		||||
        $this->factory_name = ClientFactory::class;
 | 
			
		||||
        $this->repository = app()->make($this->repository_name);
 | 
			
		||||
        $this->repository->import_mode = true;
 | 
			
		||||
        $this->transformer = new ClientTransformer($this->company);
 | 
			
		||||
        $client_count = $this->ingest($data, $entity_type);
 | 
			
		||||
        $this->entity_count['clients'] = $client_count;
 | 
			
		||||
    } 
 | 
			
		||||
 | 
			
		||||
    public function product()
 | 
			
		||||
    {
 | 
			
		||||
        $entity_type = 'product';
 | 
			
		||||
        $data = $this->getData($entity_type);
 | 
			
		||||
        if (empty($data)) {
 | 
			
		||||
            $this->entity_count['products'] = 0;
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->request_name = StoreProductRequest::class;
 | 
			
		||||
        $this->repository_name = ProductRepository::class;
 | 
			
		||||
        $this->factory_name = ProductFactory::class;
 | 
			
		||||
        $this->repository = app()->make($this->repository_name);
 | 
			
		||||
        $this->repository->import_mode = true;
 | 
			
		||||
        $this->transformer = new ProductTransformer($this->company);
 | 
			
		||||
        $count = $this->ingest($data, $entity_type);
 | 
			
		||||
        $this->entity_count['products'] = $count;
 | 
			
		||||
    } 
 | 
			
		||||
 | 
			
		||||
    public function getData($type) {
 | 
			
		||||
 | 
			
		||||
        // get the data from cache? file? or api ?
 | 
			
		||||
        return json_decode(base64_decode(Cache::get("{$this->hash}-$type")), 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function payment()
 | 
			
		||||
    {
 | 
			
		||||
        $entity_type = 'payment';
 | 
			
		||||
        $data = $this->getData($entity_type);
 | 
			
		||||
        if (empty($data)) {
 | 
			
		||||
            $this->entity_count['payments'] = 0;
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->request_name = StorePaymentRequest::class;
 | 
			
		||||
        $this->repository_name = PaymentRepository::class;
 | 
			
		||||
        $this->factory_name = PaymentFactory::class;
 | 
			
		||||
        $this->repository = app()->make($this->repository_name);
 | 
			
		||||
        $this->repository->import_mode = true;
 | 
			
		||||
        $this->transformer = new PaymentTransformer($this->company);
 | 
			
		||||
        $count = $this->ingest($data, $entity_type);
 | 
			
		||||
        $this->entity_count['payments'] = $count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function invoice()
 | 
			
		||||
    {
 | 
			
		||||
        //make sure we update and create products 
 | 
			
		||||
        $initial_update_products_value = $this->company->update_products;
 | 
			
		||||
        $this->company->update_products = true;
 | 
			
		||||
 | 
			
		||||
        $this->company->save();
 | 
			
		||||
 | 
			
		||||
        $entity_type = 'invoice';
 | 
			
		||||
        $data = $this->getData($entity_type);
 | 
			
		||||
 | 
			
		||||
        if (empty($data)) {
 | 
			
		||||
            $this->entity_count['invoices'] = 0;
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->request_name = StoreInvoiceRequest::class;
 | 
			
		||||
        $this->repository_name = InvoiceRepository::class;
 | 
			
		||||
        $this->factory_name = InvoiceFactory::class;
 | 
			
		||||
        $this->repository = app()->make($this->repository_name);
 | 
			
		||||
        $this->repository->import_mode = true;
 | 
			
		||||
        $this->transformer = new InvoiceTransformer($this->company);
 | 
			
		||||
        $invoice_count = $this->ingestInvoices($data,'');
 | 
			
		||||
        $this->entity_count['invoices'] = $invoice_count;
 | 
			
		||||
        $this->company->update_products = $initial_update_products_value;
 | 
			
		||||
        $this->company->save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function ingestInvoices($invoices, $invoice_number_key)
 | 
			
		||||
    {
 | 
			
		||||
        $count = 0;
 | 
			
		||||
        $invoice_transformer = $this->transformer;
 | 
			
		||||
        /** @var ClientRepository $client_repository */
 | 
			
		||||
        $client_repository = app()->make(ClientRepository::class);
 | 
			
		||||
        $client_repository->import_mode = true;
 | 
			
		||||
        $invoice_repository = new InvoiceRepository();
 | 
			
		||||
        $invoice_repository->import_mode = true;
 | 
			
		||||
        
 | 
			
		||||
        foreach ($invoices as $raw_invoice) {
 | 
			
		||||
            if(!is_array($raw_invoice)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                $invoice_data = $invoice_transformer->transform($raw_invoice);
 | 
			
		||||
                $invoice_data['user_id'] = $this->company->owner()->id;
 | 
			
		||||
                $invoice_data['line_items'] = (array) $invoice_data['line_items'];
 | 
			
		||||
                $invoice_data['line_items'] = $this->cleanItems(
 | 
			
		||||
                    $invoice_data['line_items'] ?? []
 | 
			
		||||
                );
 | 
			
		||||
                
 | 
			
		||||
                if (
 | 
			
		||||
                    empty($invoice_data['client_id']) &&
 | 
			
		||||
                    ! empty($invoice_data['client'])
 | 
			
		||||
                ) {
 | 
			
		||||
                    $client_data = $invoice_data['client'];
 | 
			
		||||
                    $client_data['user_id'] = $this->getUserIDForRecord(
 | 
			
		||||
                        $invoice_data
 | 
			
		||||
                    );
 | 
			
		||||
                    $client_repository->save(
 | 
			
		||||
                        $client_data,
 | 
			
		||||
                        $client = ClientFactory::create(
 | 
			
		||||
                            $this->company->id,
 | 
			
		||||
                            $client_data['user_id']
 | 
			
		||||
                        )
 | 
			
		||||
                    );
 | 
			
		||||
                    $invoice_data['client_id'] = $client->id;
 | 
			
		||||
                    unset($invoice_data['client']);
 | 
			
		||||
                } 
 | 
			
		||||
 | 
			
		||||
                $validator = $this->request_name::runFormRequest($invoice_data);
 | 
			
		||||
                if ($validator->fails()) {
 | 
			
		||||
                    $this->error_array['invoice'][] = [
 | 
			
		||||
                        'invoice' => $invoice_data,
 | 
			
		||||
                        'error' => $validator->errors()->all(),
 | 
			
		||||
                    ];
 | 
			
		||||
                } else {
 | 
			
		||||
                    if(!Invoice::where('number',$invoice_data['number'])->get()->first())
 | 
			
		||||
                    {
 | 
			
		||||
                        $invoice = InvoiceFactory::create(
 | 
			
		||||
                            $this->company->id,
 | 
			
		||||
                            $this->company->owner()->id
 | 
			
		||||
                        );
 | 
			
		||||
                        $invoice->mergeFillable(['partial','amount','balance','line_items']);
 | 
			
		||||
                        if (! empty($invoice_data['status_id'])) {
 | 
			
		||||
                            $invoice->status_id = $invoice_data['status_id'];
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        $saveable_invoice_data = $invoice_data;
 | 
			
		||||
                        if(array_key_exists('payments', $saveable_invoice_data)) {
 | 
			
		||||
                            unset($saveable_invoice_data['payments']);
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        $invoice->fill($saveable_invoice_data);
 | 
			
		||||
                        $invoice->save();
 | 
			
		||||
                        $count++;
 | 
			
		||||
                        
 | 
			
		||||
                    }
 | 
			
		||||
                    // $this->actionInvoiceStatus(
 | 
			
		||||
                    //     $invoice,
 | 
			
		||||
                    //     $invoice_data,
 | 
			
		||||
                    //     $invoice_repository
 | 
			
		||||
                    // );
 | 
			
		||||
                }
 | 
			
		||||
            } catch (\Exception $ex) {
 | 
			
		||||
                if (\DB::connection(config('database.default'))->transactionLevel() > 0) {
 | 
			
		||||
                    \DB::connection(config('database.default'))->rollBack();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ($ex instanceof ImportException) {
 | 
			
		||||
                    $message = $ex->getMessage();
 | 
			
		||||
                } else {
 | 
			
		||||
                    report($ex);
 | 
			
		||||
                    $message = 'Unknown error ';
 | 
			
		||||
                    nlog($ex->getMessage());
 | 
			
		||||
                    nlog($raw_invoice);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $this->error_array['invoice'][] = [
 | 
			
		||||
                    'invoice' => $raw_invoice,
 | 
			
		||||
                    'error' => $message,
 | 
			
		||||
                ];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										95
									
								
								app/Import/Transformer/Quickbooks/ClientTransformer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								app/Import/Transformer/Quickbooks/ClientTransformer.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,95 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Invoice Ninja (https://clientninja.com).
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
 | 
			
		||||
 *
 | 
			
		||||
 * @license https://www.elastic.co/licensing/elastic-license
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace App\Import\Transformer\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use App\Import\Transformer\Quickbooks\CommonTrait;
 | 
			
		||||
use App\Import\Transformer\BaseTransformer;
 | 
			
		||||
use App\Models\Client as Model;
 | 
			
		||||
use App\Models\ClientContact;
 | 
			
		||||
use App\Import\ImportException;
 | 
			
		||||
use Illuminate\Support\Str;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class ClientTransformer.
 | 
			
		||||
 */
 | 
			
		||||
class ClientTransformer extends BaseTransformer
 | 
			
		||||
{
 | 
			
		||||
    
 | 
			
		||||
    use CommonTrait {
 | 
			
		||||
        transform as preTransform;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private $fillable = [
 | 
			
		||||
        'name'              => 'CompanyName',
 | 
			
		||||
        'phone'             => 'PrimaryPhone.FreeFormNumber',
 | 
			
		||||
        'country_id'        => 'BillAddr.Country',
 | 
			
		||||
        'state'             => 'BillAddr.CountrySubDivisionCode',
 | 
			
		||||
        'address1'          => 'BillAddr.Line1',
 | 
			
		||||
        'city'              => 'BillAddr.City',
 | 
			
		||||
        'postal_code'       => 'BillAddr.PostalCode',
 | 
			
		||||
        'shipping_country_id' => 'ShipAddr.Country',
 | 
			
		||||
        'shipping_state'    => 'ShipAddr.CountrySubDivisionCode',
 | 
			
		||||
        'shipping_address1' => 'ShipAddr.Line1',
 | 
			
		||||
        'shipping_city'     => 'ShipAddr.City',
 | 
			
		||||
        'shipping_postal_code' => 'ShipAddr.PostalCode',
 | 
			
		||||
        'public_notes'      => 'Notes'
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function __construct($company)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($company);
 | 
			
		||||
 | 
			
		||||
        $this->model = new Model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Transforms a Customer array into a ClientContact model.
 | 
			
		||||
     *
 | 
			
		||||
     * @param array $data
 | 
			
		||||
     * @return array|bool
 | 
			
		||||
     */
 | 
			
		||||
    public function transform($data)
 | 
			
		||||
    {
 | 
			
		||||
        $transformed_data = [];
 | 
			
		||||
        // Assuming 'customer_name' is equivalent to 'CompanyName'
 | 
			
		||||
        if (isset($data['CompanyName']) && $this->hasClient($data['CompanyName'])) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $transformed_data = $this->preTransform($data);
 | 
			
		||||
        $transformed_data['contacts'][0] = $this->getContacts($data)->toArray()+['company_id' => $this->company->id ];
 | 
			
		||||
        
 | 
			
		||||
        return $transformed_data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function getContacts($data) {
 | 
			
		||||
        return (new ClientContact())->fill([
 | 
			
		||||
                    'first_name'    => $this->getString($data, 'GivenName'),
 | 
			
		||||
                    'last_name'     => $this->getString($data, 'FamilyName'),
 | 
			
		||||
                    'phone'         => $this->getString($data, 'PrimaryPhone.FreeFormNumber'),
 | 
			
		||||
                    'email'         => $this->getString($data, 'PrimaryEmailAddr.Address'),
 | 
			
		||||
                    'company_id' => $this->company->id
 | 
			
		||||
                ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function getShipAddrCountry($data,$field) {
 | 
			
		||||
        return is_null(($c = $this->getString($data,$field))) ? null : $this->getCountryId($c);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getBillAddrCountry($data,$field) {
 | 
			
		||||
        return is_null(($c = $this->getString($data,$field))) ? null : $this->getCountryId($c);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								app/Import/Transformer/Quickbooks/CommonTrait.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								app/Import/Transformer/Quickbooks/CommonTrait.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Import\Transformer\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Support\Arr;
 | 
			
		||||
 | 
			
		||||
trait CommonTrait
 | 
			
		||||
{
 | 
			
		||||
    protected $model;
 | 
			
		||||
 | 
			
		||||
    public function getString($data,$field) {
 | 
			
		||||
        return Arr::get($data,$field);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getCreateTime($data, $field = null)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->parseDateOrNull($data, 'MetaData.CreateTime');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLastUpdatedTime($data, $field = null)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->parseDateOrNull($data,'MetaData.LastUpdatedTime');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function transform($data)
 | 
			
		||||
    {
 | 
			
		||||
        $transformed = [];
 | 
			
		||||
 | 
			
		||||
        foreach ($this->fillable as $key => $field) {
 | 
			
		||||
            $transformed[$key] = is_null((($v = $this->getString($data, $field))))? null : (method_exists($this, ($method = "get{$field}")) ? call_user_func([$this, $method], $data, $field ) : $this->getString($data,$field));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->model->fillable(array_keys($this->fillable))->fill($transformed)->toArray() + ['company_id' => $this->company->id ] ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										196
									
								
								app/Import/Transformer/Quickbooks/InvoiceTransformer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								app/Import/Transformer/Quickbooks/InvoiceTransformer.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,196 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Invoice Ninja (https://invoiceninja.com).
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
 | 
			
		||||
 *
 | 
			
		||||
 * @license https://www.elastic.co/licensing/elastic-license
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace App\Import\Transformer\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Support\Str;
 | 
			
		||||
use Illuminate\Support\Arr;
 | 
			
		||||
use App\Import\ImportException;
 | 
			
		||||
use App\DataMapper\InvoiceItem;
 | 
			
		||||
use App\Models\Invoice as Model;
 | 
			
		||||
use App\Import\Transformer\BaseTransformer;
 | 
			
		||||
use App\Import\Transformer\Quickbooks\CommonTrait;
 | 
			
		||||
use App\Import\Transformer\Quickbooks\ClientTransformer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class InvoiceTransformer.
 | 
			
		||||
 */
 | 
			
		||||
class InvoiceTransformer extends BaseTransformer
 | 
			
		||||
{
 | 
			
		||||
    use CommonTrait {
 | 
			
		||||
        transform as preTransform;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private $fillable = [
 | 
			
		||||
        'amount' => "TotalAmt",
 | 
			
		||||
        'line_items' => "Line",
 | 
			
		||||
        'due_date' => "DueDate",
 | 
			
		||||
        'partial' => "Deposit",
 | 
			
		||||
        'balance' => "Balance",
 | 
			
		||||
        'private_notes' => "CustomerMemo",
 | 
			
		||||
        'public_notes' => "CustomerMemo",
 | 
			
		||||
        'number' => "DocNumber",
 | 
			
		||||
        'created_at' => "CreateTime",
 | 
			
		||||
        'updated_at' => "LastUpdatedTime",
 | 
			
		||||
        'payments' => 'LinkedTxn',
 | 
			
		||||
        'status_id' => 'InvoiceStatus',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function __construct($company)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($company);
 | 
			
		||||
 | 
			
		||||
        $this->model = new Model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getInvoiceStatus($data)
 | 
			
		||||
    {
 | 
			
		||||
        return Invoice::STATUS_SENT;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function transform($data)
 | 
			
		||||
    {
 | 
			
		||||
       return $this->preTransform($data) + $this->getInvoiceClient($data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getTotalAmt($data)
 | 
			
		||||
    {
 | 
			
		||||
        return (float) $this->getString($data,'TotalAmt');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLine($data)
 | 
			
		||||
    {
 | 
			
		||||
        return array_map(function ($item) {
 | 
			
		||||
            return [
 | 
			
		||||
                'description' => $this->getString($item,'Description'),
 | 
			
		||||
                'product_key' => $this->getString($item,'Description'),
 | 
			
		||||
                'quantity' => (int) $this->getString($item,'SalesItemLineDetail.Qty'),
 | 
			
		||||
                'unit_price' =>(double) $this->getString($item,'SalesItemLineDetail.UnitPrice'),
 | 
			
		||||
                'line_total' => (double) $this->getString($item,'Amount'),
 | 
			
		||||
                'cost' =>(double) $this->getString($item,'SalesItemLineDetail.UnitPrice'),
 | 
			
		||||
                'product_cost' => (double) $this->getString($item,'SalesItemLineDetail.UnitPrice'),
 | 
			
		||||
                'tax_amount' => (double) $this->getString($item,'TxnTaxDetail.TotalTax'),
 | 
			
		||||
            ];
 | 
			
		||||
        }, array_filter($this->getString($data,'Line'), function ($item) {
 | 
			
		||||
            return $this->getString($item,'DetailType') !== 'SubTotalLineDetail';
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getInvoiceClient($data, $field = null) {
 | 
			
		||||
        /**
 | 
			
		||||
         *  "CustomerRef": {
 | 
			
		||||
                "value": "23",
 | 
			
		||||
                "name": ""Barnett Design
 | 
			
		||||
                },
 | 
			
		||||
                "CustomerMemo": {
 | 
			
		||||
                "value": "Thank you for your business and have a great day!"
 | 
			
		||||
                },
 | 
			
		||||
                "BillAddr": {
 | 
			
		||||
                "Id": "58",
 | 
			
		||||
                "Line1": "Shara Barnett",
 | 
			
		||||
                "Line2": "Barnett Design",
 | 
			
		||||
                "Line3": "19 Main St.",
 | 
			
		||||
                "Line4": "Middlefield, CA  94303",
 | 
			
		||||
                "Lat": "37.4530553",
 | 
			
		||||
                "Long": "-122.1178261"
 | 
			
		||||
                },
 | 
			
		||||
                "ShipAddr": {
 | 
			
		||||
                "Id": "24",
 | 
			
		||||
                "Line1": "19 Main St.",
 | 
			
		||||
                "City": "Middlefield",
 | 
			
		||||
                "CountrySubDivisionCode": "CA",
 | 
			
		||||
                "PostalCode": "94303",
 | 
			
		||||
                "Lat": "37.445013",
 | 
			
		||||
                "Long": "-122.1391443"
 | 
			
		||||
                },"BillEmail": {
 | 
			
		||||
                "Address": "Design@intuit.com"
 | 
			
		||||
                },
 | 
			
		||||
                    [
 | 
			
		||||
                    'name'              => 'CompanyName',
 | 
			
		||||
                    'phone'             => 'PrimaryPhone.FreeFormNumber',
 | 
			
		||||
                    'country_id'        => 'BillAddr.Country',
 | 
			
		||||
                    'state'             => 'BillAddr.CountrySubDivisionCode',
 | 
			
		||||
                    'address1'          => 'BillAddr.Line1',
 | 
			
		||||
                    'city'              => 'BillAddr.City',
 | 
			
		||||
                    'postal_code'       => 'BillAddr.PostalCode',
 | 
			
		||||
                    'shipping_country_id' => 'ShipAddr.Country',
 | 
			
		||||
                    'shipping_state'    => 'ShipAddr.CountrySubDivisionCode',
 | 
			
		||||
                    'shipping_address1' => 'ShipAddr.Line1',
 | 
			
		||||
                    'shipping_city'     => 'ShipAddr.City',
 | 
			
		||||
                    'shipping_postal_code' => 'ShipAddr.PostalCode',
 | 
			
		||||
                    'public_notes'      => 'Notes'
 | 
			
		||||
                ];
 | 
			
		||||
 | 
			
		||||
         */
 | 
			
		||||
        $bill_address = (object) $this->getString($data, 'BillAddr');
 | 
			
		||||
        $ship_address = $this->getString($data, 'ShipAddr');
 | 
			
		||||
        $customer = explode(" ", $this->getString($data, 'CustomerRef.name'));
 | 
			
		||||
        $customer = ['GivenName' => $customer[0], 'FamilyName' => $customer[1]];
 | 
			
		||||
        $has_company = property_exists($bill_address, 'Line4');
 | 
			
		||||
        $address = $has_company?  $bill_address->Line4 : $bill_address->Line3;
 | 
			
		||||
        $address_1 = substr($address, 0, stripos($address,','));
 | 
			
		||||
        $address =array_filter( [$address_1] + (explode(' ', substr($address, stripos($address,",") + 1 ))));
 | 
			
		||||
        $client_id = null;
 | 
			
		||||
        $client = 
 | 
			
		||||
        [
 | 
			
		||||
            "CompanyName" => $has_company?  $bill_address->Line2 : $bill_address->Line1,
 | 
			
		||||
            "BillAddr" => array_combine(['City','CountrySubDivisionCode','PostalCode'], array_pad($address,3,'N/A') ) + ['Line1' => $has_company? $bill_address->Line3 : $bill_address->Line2 ],
 | 
			
		||||
            "ShipAddr" => $ship_address
 | 
			
		||||
        ] + $customer + ['PrimaryEmailAddr' => ['Address' => $this->getString($data, 'BillEmail.Address') ]];
 | 
			
		||||
        if($this->hasClient($client['CompanyName']))
 | 
			
		||||
        {
 | 
			
		||||
            $client_id = $this->getClient($client['CompanyName'],$this->getString($client, 'PrimaryEmailAddr.Address'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
       
 | 
			
		||||
        return ['client'=> (new ClientTransformer($this->company))->transform($client), 'client_id'=> $client_id ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getDueDate($data)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->parseDateOrNull($data, 'DueDate');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getDeposit($data)
 | 
			
		||||
    {
 | 
			
		||||
        return (double) $this->getString($data,'Deposit');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getBalance($data)
 | 
			
		||||
    {
 | 
			
		||||
        return (double) $this->getString($data,'Balance');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getCustomerMemo($data)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getString($data,'CustomerMemo.value');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getDocNumber($data, $field = null) 
 | 
			
		||||
    {
 | 
			
		||||
        return sprintf("%s-%s", 
 | 
			
		||||
                    $this->getString($data, 'DocNumber'), 
 | 
			
		||||
                    $this->getString($data, 'Id.value')
 | 
			
		||||
                );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLinkedTxn($data)
 | 
			
		||||
    {
 | 
			
		||||
        $payments = $this->getString($data,'LinkedTxn');
 | 
			
		||||
        if(empty($payments)) return [];
 | 
			
		||||
 | 
			
		||||
       return [[
 | 
			
		||||
            'amount' => $this->getTotalAmt($data),
 | 
			
		||||
            'date' => $this->parseDateOrNull($data, 'TxnDate')
 | 
			
		||||
        ]];
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										99
									
								
								app/Import/Transformer/Quickbooks/PaymentTransformer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								app/Import/Transformer/Quickbooks/PaymentTransformer.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,99 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Invoice Ninja (https://Paymentninja.com).
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
 | 
			
		||||
 *
 | 
			
		||||
 * @license https://www.elastic.co/licensing/elastic-license
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace App\Import\Transformer\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use App\Import\Transformer\Quickbooks\CommonTrait;
 | 
			
		||||
use App\Import\Transformer\BaseTransformer;
 | 
			
		||||
use App\Models\Payment as Model;
 | 
			
		||||
use App\Import\ImportException;
 | 
			
		||||
use Illuminate\Support\Str;
 | 
			
		||||
use Illuminate\Support\Arr;
 | 
			
		||||
use App\Models\Invoice;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 
 | 
			
		||||
 * Class PaymentTransformer.
 | 
			
		||||
 */
 | 
			
		||||
class PaymentTransformer extends BaseTransformer
 | 
			
		||||
{
 | 
			
		||||
    use CommonTrait;
 | 
			
		||||
 | 
			
		||||
    protected $fillable = [
 | 
			
		||||
        'number' => "PaymentRefNum",
 | 
			
		||||
        'amount' => "TotalAmt",
 | 
			
		||||
        "client_id" => "CustomerRef",
 | 
			
		||||
        "currency_id" => "CurrencyRef",
 | 
			
		||||
        'date' => "TxnDate",
 | 
			
		||||
        "invoices" => "Line",
 | 
			
		||||
        'private_notes' => "PrivateNote",
 | 
			
		||||
        'created_at' => "CreateTime",
 | 
			
		||||
        'updated_at' => "LastUpdatedTime"
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function __construct($company)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($company);
 | 
			
		||||
 | 
			
		||||
        $this->model = new Model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getTotalAmt($data, $field = null) {
 | 
			
		||||
        return (float) $this->getString($data, $field);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getTxnDate($data, $field = null)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->parseDateOrNull($data, $field);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getCustomerRef($data, $field = null )
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getClient($this->getString($data, 'CustomerRef.name'),null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getCurrencyRef($data, $field = null) 
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getCurrencyByCode($data['CurrencyRef'], 'value');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLine($data, $field = null)
 | 
			
		||||
    {
 | 
			
		||||
        $invoices = [];
 | 
			
		||||
        $invoice = $this->getString($data,'Line.LinkedTxn.TxnType');
 | 
			
		||||
        if(is_null($invoice) || $invoice !== 'Invoice') return $invoices;
 | 
			
		||||
        if( is_null( ($invoice_id = $this->getInvoiceId($this->getString($data, 'Line.LinkedTxn.TxnId.value')))) ) return $invoices;
 | 
			
		||||
        
 | 
			
		||||
        return [[
 | 
			
		||||
            'amount' => (float) $this->getString($data, 'Line.Amount'),
 | 
			
		||||
            'invoice_id' => $invoice_id
 | 
			
		||||
        ]];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
     /**
 | 
			
		||||
     * @param $invoice_number
 | 
			
		||||
     *
 | 
			
		||||
     * @return int|null
 | 
			
		||||
     */
 | 
			
		||||
    public function getInvoiceId($invoice_number)
 | 
			
		||||
    {
 | 
			
		||||
        $invoice = Invoice::query()->where('company_id', $this->company->id)
 | 
			
		||||
            ->where('is_deleted', false)
 | 
			
		||||
            ->where("number", "LIKE", 
 | 
			
		||||
                "%-$invoice_number%",
 | 
			
		||||
            )
 | 
			
		||||
            ->first();
 | 
			
		||||
 | 
			
		||||
        return $invoice ? $invoice->id : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										59
									
								
								app/Import/Transformer/Quickbooks/ProductTransformer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/Import/Transformer/Quickbooks/ProductTransformer.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Invoice Ninja (https://Productninja.com).
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://github.com/invoiceninja/invoiceninja source repository
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
 | 
			
		||||
 *
 | 
			
		||||
 * @license https://www.elastic.co/licensing/elastic-license
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace App\Import\Transformer\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use App\Import\Transformer\Quickbooks\CommonTrait;
 | 
			
		||||
use App\Import\Transformer\BaseTransformer;
 | 
			
		||||
use App\Models\Product as Model;
 | 
			
		||||
use App\Import\ImportException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class ProductTransformer.
 | 
			
		||||
 */
 | 
			
		||||
class ProductTransformer extends BaseTransformer
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    use CommonTrait;
 | 
			
		||||
    
 | 
			
		||||
    protected $fillable = [
 | 
			
		||||
        'product_key' => 'Name',
 | 
			
		||||
        'notes' => 'Description',
 | 
			
		||||
        'cost' => 'PurchaseCost',
 | 
			
		||||
        'price' => 'UnitPrice',
 | 
			
		||||
        'quantity' => 'QtyOnHand',
 | 
			
		||||
        'in_stock_quantity' => 'QtyOnHand',
 | 
			
		||||
        'created_at' => 'CreateTime',
 | 
			
		||||
        'updated_at' => 'LastUpdatedTime',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function __construct($company)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($company);
 | 
			
		||||
 | 
			
		||||
        $this->model = new Model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getQtyOnHand($data, $field = null) {
 | 
			
		||||
        return (int) $this->getString($data, $field);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getPurchaseCost($data, $field = null) {
 | 
			
		||||
        return (double) $this->getString($data, $field);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function getUnitPrice($data, $field = null) {
 | 
			
		||||
        return (float) $this->getString($data, $field);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								app/Jobs/Import/QuickbooksIngest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								app/Jobs/Import/QuickbooksIngest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,45 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Jobs\Import;
 | 
			
		||||
 | 
			
		||||
use App\Libraries\MultiDB;
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use App\Import\Providers\Quickbooks;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
			
		||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
			
		||||
 | 
			
		||||
class QuickbooksIngest implements ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 | 
			
		||||
 | 
			
		||||
    protected $engine;
 | 
			
		||||
    protected $request;
 | 
			
		||||
    protected $company;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a new job instance.
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct(array $request, $company)
 | 
			
		||||
    {
 | 
			
		||||
        $this->company = $company;
 | 
			
		||||
        $this->request = $request;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute the job.
 | 
			
		||||
     */
 | 
			
		||||
    public function handle(): void
 | 
			
		||||
    {
 | 
			
		||||
        MultiDB::setDb($this->company->db);
 | 
			
		||||
        set_time_limit(0);
 | 
			
		||||
 | 
			
		||||
        $engine = new Quickbooks(['import_type' => 'client', 'hash'=> $this->request['hash'] ], $this->company);
 | 
			
		||||
        foreach ($this->request['import_types'] as $entity) {
 | 
			
		||||
            $engine->import($entity);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $engine->finalizeImport();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										84
									
								
								app/Providers/QuickbooksServiceProvider.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								app/Providers/QuickbooksServiceProvider.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,84 @@
 | 
			
		||||
<?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');
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,12 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Repositories\Import\Quickbooks\Contracts;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
 | 
			
		||||
interface RepositoryInterface {
 | 
			
		||||
 | 
			
		||||
   function get(int $max = 100): Collection;
 | 
			
		||||
   function all(): Collection;
 | 
			
		||||
   function count(): int;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								app/Repositories/Import/Quickbooks/CustomerRepository.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/Repositories/Import/Quickbooks/CustomerRepository.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Repositories\Import\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use App\Repositories\Import\Quickbooks\Repository;
 | 
			
		||||
use App\Repositories\Import\Quickbooks\Contracts\RepositoryInterface;
 | 
			
		||||
 | 
			
		||||
class CustomerRepository extends Repository implements RepositoryInterface
 | 
			
		||||
{
 | 
			
		||||
    protected string $entity = "Customer";
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								app/Repositories/Import/Quickbooks/InvoiceRepository.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/Repositories/Import/Quickbooks/InvoiceRepository.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Repositories\Import\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use App\Repositories\Import\Quickbooks\Contracts\RepositoryInterface as QuickbooksInterface;
 | 
			
		||||
 | 
			
		||||
class InvoiceRepository extends Repository implements QuickbooksInterface
 | 
			
		||||
{
 | 
			
		||||
    protected string $entity = "Invoice";
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								app/Repositories/Import/Quickbooks/ItemRepository.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/Repositories/Import/Quickbooks/ItemRepository.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Repositories\Import\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use App\Repositories\Import\Quickbooks\Repository;
 | 
			
		||||
use App\Repositories\Import\Quickbooks\Contracts\RepositoryInterface;
 | 
			
		||||
 | 
			
		||||
class ItemRepository extends Repository implements RepositoryInterface
 | 
			
		||||
{
 | 
			
		||||
    protected string $entity = "Item";
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								app/Repositories/Import/Quickbooks/PaymentRepository.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/Repositories/Import/Quickbooks/PaymentRepository.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Repositories\Import\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use App\Repositories\Import\Quickbooks\Contracts\RepositoryInterface as QuickbooksInterface;
 | 
			
		||||
 | 
			
		||||
class PaymentRepository extends Repository implements QuickbooksInterface
 | 
			
		||||
{
 | 
			
		||||
    protected string $entity = "Payment";
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								app/Repositories/Import/Quickbooks/Repository.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								app/Repositories/Import/Quickbooks/Repository.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Repositories\Import\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use App\Repositories\Import\Quickbooks\Contracts\RepositoryInterface;
 | 
			
		||||
use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface;
 | 
			
		||||
use App\Repositories\Import\Quickbooks\Transformers\Transformer as QuickbooksTransformer;
 | 
			
		||||
 | 
			
		||||
abstract class Repository implements RepositoryInterface
 | 
			
		||||
{
 | 
			
		||||
    
 | 
			
		||||
    protected string $entity;
 | 
			
		||||
    protected QuickbooksInterface $db;
 | 
			
		||||
    protected QuickbooksTransformer $transfomer;
 | 
			
		||||
 | 
			
		||||
    public function __construct(QuickbooksInterface $db, QuickbooksTransformer $transfomer)
 | 
			
		||||
    {
 | 
			
		||||
        $this->db= $db;
 | 
			
		||||
        $this->transformer = $transfomer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function count() : int {
 | 
			
		||||
        return $this->db->totalRecords($this->entity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function all() : Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->get($this->count());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function get(int $max = 100): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->transformer->transform($this->db->fetchRecords($this->entity, $max), $this->entity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,86 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Repositories\Import\Quickbooks\Transformers;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
 | 
			
		||||
class Transformer
 | 
			
		||||
{
 | 
			
		||||
    public function transform(array $items, string $type): Collection
 | 
			
		||||
    {
 | 
			
		||||
        if(!method_exists($this, ($method = "transform{$type}s"))) throw new \InvalidArgumentException("Unknown type: $type");
 | 
			
		||||
 | 
			
		||||
        return call_user_func([$this, $method], $items);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function transformCustomers(array $items): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->transformation($items, [
 | 
			
		||||
            'CompanyName',
 | 
			
		||||
            'PrimaryPhone',
 | 
			
		||||
            'BillAddr',
 | 
			
		||||
            'ShipAddr',
 | 
			
		||||
            'Notes',
 | 
			
		||||
            'GivenName',
 | 
			
		||||
            'FamilyName',
 | 
			
		||||
            'PrimaryEmailAddr',
 | 
			
		||||
            'CurrencyRef',
 | 
			
		||||
            'MetaData'
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function transformInvoices(array $items): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->transformation($items, [
 | 
			
		||||
            "TotalAmt",
 | 
			
		||||
            "Line",
 | 
			
		||||
            "DueDate",
 | 
			
		||||
            "Deposit",
 | 
			
		||||
            "Balance",
 | 
			
		||||
            "CustomerMemo",
 | 
			
		||||
            "DocNumber",
 | 
			
		||||
            "CustomerRef",
 | 
			
		||||
            "BillEmail",
 | 
			
		||||
            'MetaData',
 | 
			
		||||
            "BillAddr",
 | 
			
		||||
            "ShipAddr",
 | 
			
		||||
            "LinkedTxn",
 | 
			
		||||
            "Id",
 | 
			
		||||
            "CurrencyRef",
 | 
			
		||||
            "TxnTaxDetail",
 | 
			
		||||
            "TxnDate"
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function transformPayments(array $items): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->transformation($items, [
 | 
			
		||||
            "PaymentRefNum",
 | 
			
		||||
            "TotalAmt",
 | 
			
		||||
            "CustomerRef",
 | 
			
		||||
            "CurrencyRef",
 | 
			
		||||
            "TxnDate",
 | 
			
		||||
            "Line",
 | 
			
		||||
            "PrivateNote",
 | 
			
		||||
            "MetaData"
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function transformItems(array $items): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->transformation($items, [
 | 
			
		||||
            'Name',
 | 
			
		||||
            'Description',
 | 
			
		||||
            'PurchaseCost',
 | 
			
		||||
            'UnitPrice',
 | 
			
		||||
            'QtyOnHand',
 | 
			
		||||
            'MetaData'
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function transformation(array $items, array $keys) : Collection
 | 
			
		||||
    {
 | 
			
		||||
        return collect($items)->select($keys);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								app/Services/Import/Quickbooks/Auth.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								app/Services/Import/Quickbooks/Auth.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,66 @@
 | 
			
		||||
<?php
 | 
			
		||||
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 accessToken(string $code, string $realm ) : array
 | 
			
		||||
    {
 | 
			
		||||
       // TODO: Get or put token in Cache or DB?
 | 
			
		||||
        return $this->sdk->accessToken($code, $realm);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function refreshToken() : array
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: Get or put token in Cache or DB?
 | 
			
		||||
        return  $this->sdk->refreshToken();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getAuthorizationUrl(): string 
 | 
			
		||||
    {
 | 
			
		||||
        return $this->sdk->getAuthorizationUrl();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getState() : string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->sdk->getState();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function saveTokens($key, $tokens)
 | 
			
		||||
    {
 | 
			
		||||
        $token_store = new CompanyTokensRepository($key);
 | 
			
		||||
        $token_store->save($tokens); 
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
    {
 | 
			
		||||
        return  $this->getAccessToken();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								app/Services/Import/Quickbooks/Contracts/SdkInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								app/Services/Import/Quickbooks/Contracts/SdkInterface.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Services\Import\Quickbooks\Contracts;
 | 
			
		||||
 | 
			
		||||
interface SdkInterface
 | 
			
		||||
{
 | 
			
		||||
    function getAuthorizationUrl(): string;
 | 
			
		||||
    function accessToken(string $code, string $realm): array;
 | 
			
		||||
    function refreshToken(): array;
 | 
			
		||||
    function getAccessToken(): array;
 | 
			
		||||
    function getRefreshToken(): array;
 | 
			
		||||
    function totalRecords(string $entity): int;
 | 
			
		||||
    function fetchRecords(string $entity, int $max): array;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,76 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Services\Import\Quickbooks\Repositories;
 | 
			
		||||
 | 
			
		||||
use App\Models\Company;
 | 
			
		||||
use App\Libraries\MultiDB;
 | 
			
		||||
use Illuminate\Support\Facades\DB;
 | 
			
		||||
use Illuminate\Support\Facades\Cache;
 | 
			
		||||
 | 
			
		||||
class CompanyTokensRepository {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private $company_key;
 | 
			
		||||
    private $store_key = "quickbooks-token";
 | 
			
		||||
 | 
			
		||||
    public function __construct(string $key = null) {
 | 
			
		||||
        $this->company_key = $key ?? auth()->user->company()->company_key ?? null;
 | 
			
		||||
        $this->store_key .= $key;
 | 
			
		||||
        $this->setCompanyDbByKey();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function save(array $tokens) {
 | 
			
		||||
        $this->updateAccessToken($tokens['access_token'], $tokens['access_token_expires']);
 | 
			
		||||
        $this->updateRefreshToken($tokens['refresh_token'], $tokens['refresh_token_expires'], $tokens['realm']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function findByCompanyKey(): ?Company
 | 
			
		||||
    {
 | 
			
		||||
        return Company::where('company_key', $this->company_key)->first();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setCompanyDbByKey() 
 | 
			
		||||
    {
 | 
			
		||||
        MultiDB::findAndSetDbByCompanyKey($this->company_key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function get() {
 | 
			
		||||
        return $this->getAccessToken() + $this->getRefreshToken();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    protected function updateRefreshToken(string $token, string $expires, string $realm)
 | 
			
		||||
    {
 | 
			
		||||
        DB::table('companies')
 | 
			
		||||
                    ->where('company_key', $this->company_key)
 | 
			
		||||
                    ->update(['quickbooks_refresh_token' => $token,
 | 
			
		||||
                                'quickbooks_realm_id' => $realm,
 | 
			
		||||
                                'quickbooks_refresh_expires' => $expires ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function updateAccessToken(string $token, string $expires )
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        Cache::put([$this->store_key => $token], $expires);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function getAccessToken( )
 | 
			
		||||
    {
 | 
			
		||||
        $result = Cache::get($this->store_key);
 | 
			
		||||
        
 | 
			
		||||
        return $result ? ['access_token' => $result] : [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function getRefreshToken()
 | 
			
		||||
    {
 | 
			
		||||
        $result = (array) DB::table('companies')
 | 
			
		||||
        ->select('quickbooks_refresh_token', 'quickbooks_realm_id')
 | 
			
		||||
        ->where('company_key',$this->company_key)
 | 
			
		||||
        ->where('quickbooks_refresh_expires','>',now())
 | 
			
		||||
        ->first();
 | 
			
		||||
        
 | 
			
		||||
        return $result? array_combine(['refresh_token','realm'], array_values($result) ) : [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										107
									
								
								app/Services/Import/Quickbooks/SdkWrapper.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								app/Services/Import/Quickbooks/SdkWrapper.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,107 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Services\Import\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface;
 | 
			
		||||
 | 
			
		||||
final class SdkWrapper implements QuickbooksInterface
 | 
			
		||||
{
 | 
			
		||||
    
 | 
			
		||||
    const MAXRESULTS = 10000;
 | 
			
		||||
 | 
			
		||||
    private $sdk;
 | 
			
		||||
    private $entities = ['Customer','Invoice','Payment','Item'];
 | 
			
		||||
 | 
			
		||||
    public function __construct($sdk)
 | 
			
		||||
    {
 | 
			
		||||
        // Prep Data Services
 | 
			
		||||
        $this->sdk = $sdk;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getAuthorizationUrl() : string 
 | 
			
		||||
    {
 | 
			
		||||
        return ($this->sdk->getOAuth2LoginHelper())->getAuthorizationCodeURL();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getState() : string
 | 
			
		||||
    {
 | 
			
		||||
        return ($this->sdk->getOAuth2LoginHelper())->getState();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getAccessToken() : array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getTokens();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getRefreshToken(): array{
 | 
			
		||||
        return $this->getTokens();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function accessToken(string $code, string $realm) : array 
 | 
			
		||||
    {
 | 
			
		||||
        $token = ($this->sdk->getOAuth2LoginHelper())->exchangeAuthorizationCodeForToken($code,$realm);
 | 
			
		||||
       
 | 
			
		||||
        return $this->getTokens();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getTokens() : array {
 | 
			
		||||
        
 | 
			
		||||
        $token =($this->sdk->getOAuth2LoginHelper())->getAccessToken();
 | 
			
		||||
        $access_token = $token->getAccessToken();
 | 
			
		||||
        $refresh_token = $token->getRefreshToken();
 | 
			
		||||
        $access_token_expires = $token->getAccessTokenExpiresAt();
 | 
			
		||||
        $refresh_token_expires = $token->getRefreshTokenExpiresAt();
 | 
			
		||||
 | 
			
		||||
        return compact('access_token', 'refresh_token','access_token_expires', 'refresh_token_expires');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function refreshToken(): array
 | 
			
		||||
    {
 | 
			
		||||
        $token = ($this->sdk->getOAuth2LoginHelper())->refreshToken();
 | 
			
		||||
        $this->sdk = $this->sdk->updateOAuth2Token($token);
 | 
			
		||||
 | 
			
		||||
        return $this->getTokens();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function handleCallbacks(array $data): void {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function totalRecords(string $entity) : int {
 | 
			
		||||
        return $this->sdk->Query("select count(*) from $entity");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function queryData(string $query, int $start = 1, $limit = 100) : array 
 | 
			
		||||
    {
 | 
			
		||||
        return (array) $this->sdk->Query($query, $start, $limit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function fetchRecords( string $entity, int $max = 1000): array {
 | 
			
		||||
        
 | 
			
		||||
        if(!in_array($entity, $this->entities)) return [];
 | 
			
		||||
        
 | 
			
		||||
        $records = [];
 | 
			
		||||
        $start = 0;
 | 
			
		||||
        $limit = 100;
 | 
			
		||||
        try {
 | 
			
		||||
            $total = $this->totalRecords($entity);
 | 
			
		||||
            $total = min($max, $total);
 | 
			
		||||
            
 | 
			
		||||
            // Step 3 & 4: Get chunks of records until the total required records are retrieved
 | 
			
		||||
            do {
 | 
			
		||||
                $limit = min(self::MAXRESULTS, $total - $start);
 | 
			
		||||
                $recordsChunk = $this->queryData("select * from $entity", $start, $limit);
 | 
			
		||||
                if(empty($recordsChunk)) break;
 | 
			
		||||
 | 
			
		||||
                $records = array_merge($records,$recordsChunk);
 | 
			
		||||
                $start += $limit;
 | 
			
		||||
            } while ($start < $total);
 | 
			
		||||
            if(empty($records)) throw new \Exceptions("No records retrieved!");
 | 
			
		||||
 | 
			
		||||
        } catch (\Throwable $th) {
 | 
			
		||||
            nlog("Fetch Quickbooks API Error: {$th->getMessage()}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $records;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										86
									
								
								app/Services/Import/Quickbooks/Service.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								app/Services/Import/Quickbooks/Service.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,86 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace App\Services\Import\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Illuminate\Support\Facades\Cache;
 | 
			
		||||
use App\Services\Import\Quickbooks\Auth;
 | 
			
		||||
use App\Repositories\Import\Quickbooks\Contracts\RepositoryInterface;
 | 
			
		||||
use App\Services\Import\QuickBooks\Contracts\SdkInterface as QuickbooksInterface;
 | 
			
		||||
 | 
			
		||||
final class Service
 | 
			
		||||
{    
 | 
			
		||||
    private QuickbooksInterface $sdk;
 | 
			
		||||
 | 
			
		||||
    public function __construct(QuickbooksInterface $quickbooks) {
 | 
			
		||||
        $this->sdk = $quickbooks;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getOAuth() : Auth
 | 
			
		||||
    {
 | 
			
		||||
        return new Auth($this->sdk);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getAccessToken() : array
 | 
			
		||||
    {
 | 
			
		||||
       return $this->getOAuth()->getAccessToken(); 
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getRefreshToken() : array
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: Check if token is Cached otherwise fetch a new one and Cache token and expire
 | 
			
		||||
        return  $this->getAccessToken();
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * fetch QuickBooks invoice records
 | 
			
		||||
     * @param int $max The maximum records to fetch. Default 100
 | 
			
		||||
     * @return Illuminate\Support\Collection;
 | 
			
		||||
     */
 | 
			
		||||
    public function fetchInvoices(int $max = 100): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->fetchRecords('Invoice', $max) ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * fetch QuickBooks payment records
 | 
			
		||||
     * @param int $max The maximum records to fetch. Default 100
 | 
			
		||||
     * @return Illuminate\Support\Collection;
 | 
			
		||||
     */
 | 
			
		||||
    public function fetchPayments(int $max = 100): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->fetchRecords('Payment', $max) ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * fetch QuickBooks product records
 | 
			
		||||
     * @param int $max The maximum records to fetch. Default 100
 | 
			
		||||
     * @return Illuminate\Support\Collection;
 | 
			
		||||
     */
 | 
			
		||||
    public function fetchItems(int $max = 100): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->fetchRecords('Item', $max) ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function fetchRecords(string $entity, $max = 100) : Collection {
 | 
			
		||||
        return (self::RepositoryFactory($entity))->get($max);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static function RepositoryFactory(string $entity) : RepositoryInterface
 | 
			
		||||
    {
 | 
			
		||||
        return app("\\App\\Repositories\\Import\Quickbooks\\{$entity}Repository");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * fetch QuickBooks customer records
 | 
			
		||||
     * @param int $max The maximum records to fetch. Default 100
 | 
			
		||||
     * @return Illuminate\Support\Collection;
 | 
			
		||||
     */
 | 
			
		||||
    public function fetchCustomers(int $max = 100): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->fetchRecords('Customer', $max) ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function totalRecords(string $entity) : int
 | 
			
		||||
    {
 | 
			
		||||
        return (self::RepositoryFactory($entity))->count();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -63,6 +63,7 @@
 | 
			
		||||
        "imdhemy/laravel-purchases": "^1.7",
 | 
			
		||||
        "intervention/image": "^2.5",
 | 
			
		||||
        "invoiceninja/einvoice": "dev-main",
 | 
			
		||||
        "invoiceninja/inspector": "^3.0",
 | 
			
		||||
        "invoiceninja/ubl_invoice": "^2",
 | 
			
		||||
        "josemmo/facturae-php": "^1.7",
 | 
			
		||||
        "laracasts/presenter": "^0.2.1",
 | 
			
		||||
@ -85,6 +86,7 @@
 | 
			
		||||
        "predis/predis": "^2",
 | 
			
		||||
        "psr/http-message": "^1.0",
 | 
			
		||||
        "pusher/pusher-php-server": "^7.2",
 | 
			
		||||
        "quickbooks/v3-php-sdk": "6.1.4-alpha",
 | 
			
		||||
        "razorpay/razorpay": "2.*",
 | 
			
		||||
        "sentry/sentry-laravel": "^4",
 | 
			
		||||
        "setasign/fpdf": "^1.8",
 | 
			
		||||
@ -197,6 +199,10 @@
 | 
			
		||||
        {
 | 
			
		||||
            "type": "vcs",
 | 
			
		||||
            "url": "https://github.com/turbo124/snappdf"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "type":"vcs",
 | 
			
		||||
            "url":"https://github.com/karneaud/QuickBooks-V3-PHP-SDK.git"
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "minimum-stability": "dev",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										130
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										130
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							@ -4,7 +4,7 @@
 | 
			
		||||
        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
 | 
			
		||||
        "This file is @generated automatically"
 | 
			
		||||
    ],
 | 
			
		||||
    "content-hash": "95e7bd229644d1d8e768ecfbc78582cd",
 | 
			
		||||
    "content-hash": "9e7ea46cfef2848f4eac13cc9c0c679a",
 | 
			
		||||
    "packages": [
 | 
			
		||||
        {
 | 
			
		||||
            "name": "adrienrn/php-mimetyper",
 | 
			
		||||
@ -4048,6 +4048,68 @@
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2024-07-22T02:40:27+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "invoiceninja/inspector",
 | 
			
		||||
            "version": "v3.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/invoiceninja/inspector.git",
 | 
			
		||||
                "reference": "29bc1ee7ae9d967287ecbd3485a2fee41a13e65f"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/invoiceninja/inspector/zipball/29bc1ee7ae9d967287ecbd3485a2fee41a13e65f",
 | 
			
		||||
                "reference": "29bc1ee7ae9d967287ecbd3485a2fee41a13e65f",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
                "doctrine/dbal": "^4.0",
 | 
			
		||||
                "illuminate/support": "^11.0",
 | 
			
		||||
                "php": "^8.2"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "orchestra/testbench": "^9.1",
 | 
			
		||||
                "phpunit/phpunit": "^11.1"
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "extra": {
 | 
			
		||||
                "laravel": {
 | 
			
		||||
                    "providers": [
 | 
			
		||||
                        "InvoiceNinja\\Inspector\\InspectorServiceProvider"
 | 
			
		||||
                    ],
 | 
			
		||||
                    "aliases": {
 | 
			
		||||
                        "Inspector": "InvoiceNinja\\Inspector\\InspectorFacade"
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "autoload": {
 | 
			
		||||
                "psr-4": {
 | 
			
		||||
                    "InvoiceNinja\\Inspector\\": "src"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "notification-url": "https://packagist.org/downloads/",
 | 
			
		||||
            "license": [
 | 
			
		||||
                "MIT"
 | 
			
		||||
            ],
 | 
			
		||||
            "authors": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Benjamin Beganović",
 | 
			
		||||
                    "email": "benjamin.beganovic4@outlook.com",
 | 
			
		||||
                    "role": "Developer"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "description": "Simplified database records management",
 | 
			
		||||
            "homepage": "https://github.com/invoiceninja/inspector",
 | 
			
		||||
            "keywords": [
 | 
			
		||||
                "inspector",
 | 
			
		||||
                "invoiceninja"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/invoiceninja/inspector/issues",
 | 
			
		||||
                "source": "https://github.com/invoiceninja/inspector/tree/v3.0"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2024-06-04T12:31:47+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "invoiceninja/ubl_invoice",
 | 
			
		||||
            "version": "v2.2.2",
 | 
			
		||||
@ -9089,6 +9151,72 @@
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2023-12-15T10:58:53+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "quickbooks/v3-php-sdk",
 | 
			
		||||
            "version": "v6.1.4-alpha",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/karneaud/QuickBooks-V3-PHP-SDK.git",
 | 
			
		||||
                "reference": "89ff2b6dcfc94634cf5806cacda1286a6898249f"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/karneaud/QuickBooks-V3-PHP-SDK/zipball/89ff2b6dcfc94634cf5806cacda1286a6898249f",
 | 
			
		||||
                "reference": "89ff2b6dcfc94634cf5806cacda1286a6898249f",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
                "ext-dom": "*",
 | 
			
		||||
                "ext-mbstring": "*",
 | 
			
		||||
                "php": ">=5.6.0"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "php-mock/php-mock": "^2.3",
 | 
			
		||||
                "php-mock/php-mock-phpunit": "^2.6",
 | 
			
		||||
                "phpunit/phpunit": "^5.0 || ^6.0 || ^7.0 || ^8"
 | 
			
		||||
            },
 | 
			
		||||
            "suggest": {
 | 
			
		||||
                "ext-curl": "Uses Curl to make HTTP Requests",
 | 
			
		||||
                "guzzlehttp/guzzle": "Uses Guzzle to make HTTP Requests"
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "autoload": {
 | 
			
		||||
                "psr-4": {
 | 
			
		||||
                    "QuickBooksOnline\\API\\": "src/"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "archive": {
 | 
			
		||||
                "exclude": [
 | 
			
		||||
                    "/docs",
 | 
			
		||||
                    "/src/Utility.Test",
 | 
			
		||||
                    "/src/XSD2PHP/docs",
 | 
			
		||||
                    "/src/XSD2PHP/test",
 | 
			
		||||
                    "/test"
 | 
			
		||||
                ]
 | 
			
		||||
            },
 | 
			
		||||
            "license": [
 | 
			
		||||
                "Apache-2.0"
 | 
			
		||||
            ],
 | 
			
		||||
            "authors": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "abisalehalliprasan",
 | 
			
		||||
                    "email": "anil_kumar3@intuit.com"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "description": "The Official PHP SDK for QuickBooks Online Accounting API",
 | 
			
		||||
            "homepage": "http://developer.intuit.com",
 | 
			
		||||
            "keywords": [
 | 
			
		||||
                "api",
 | 
			
		||||
                "http",
 | 
			
		||||
                "quickbooks",
 | 
			
		||||
                "rest",
 | 
			
		||||
                "smallbusiness"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "source": "https://github.com/karneaud/QuickBooks-V3-PHP-SDK/tree/v6.1.4-alpha"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2024-08-16T01:21:19+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "ralouphie/getallheaders",
 | 
			
		||||
            "version": "3.0.3",
 | 
			
		||||
 | 
			
		||||
@ -200,7 +200,8 @@ return [
 | 
			
		||||
        App\Providers\MultiDBProvider::class,
 | 
			
		||||
        App\Providers\ClientPortalServiceProvider::class,
 | 
			
		||||
        App\Providers\NinjaTranslationServiceProvider::class,
 | 
			
		||||
        App\Providers\StaticServiceProvider::class
 | 
			
		||||
        App\Providers\StaticServiceProvider::class,
 | 
			
		||||
        App\Providers\QuickbooksServiceProvider::class
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
@ -217,7 +218,7 @@ return [
 | 
			
		||||
    'aliases' => Facade::defaultAliases()->merge([
 | 
			
		||||
        'Collector' => Turbo124\Beacon\CollectorFacade::class,
 | 
			
		||||
        'CustomMessage' => App\Utils\ClientPortal\CustomMessage\CustomMessageFacade::class,
 | 
			
		||||
        'Redis'        => Illuminate\Support\Facades\Redis::class,
 | 
			
		||||
        'Redis'        => Illuminate\Support\Facades\Redis::class
 | 
			
		||||
    ])->toArray(),
 | 
			
		||||
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,34 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
use Illuminate\Database\Migrations\Migration;
 | 
			
		||||
use Illuminate\Database\Schema\Blueprint;
 | 
			
		||||
use Illuminate\Support\Facades\Schema;
 | 
			
		||||
 | 
			
		||||
return new class extends Migration
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Run the migrations.
 | 
			
		||||
     *
 | 
			
		||||
     * @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();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reverse the migrations.
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function down()
 | 
			
		||||
    {
 | 
			
		||||
        Schema::table('companies', function (Blueprint $table) {
 | 
			
		||||
            $table->dropColumn(['quickbooks_realm_id', 'quickbooks_refresh_token','quickbooks_refresh_expires']);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
@ -57,6 +57,7 @@ use App\Http\Controllers\SystemLogController;
 | 
			
		||||
use App\Http\Controllers\TwoFactorController;
 | 
			
		||||
use App\Http\Controllers\Auth\LoginController;
 | 
			
		||||
use App\Http\Controllers\ImportJsonController;
 | 
			
		||||
use App\Http\Controllers\ImportQuickbooksController;
 | 
			
		||||
use App\Http\Controllers\SelfUpdateController;
 | 
			
		||||
use App\Http\Controllers\TaskStatusController;
 | 
			
		||||
use App\Http\Controllers\Bank\YodleeController;
 | 
			
		||||
@ -244,7 +245,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
 | 
			
		||||
    Route::post('import', [ImportController::class, 'import'])->name('import.import');
 | 
			
		||||
    Route::post('import_json', [ImportJsonController::class, 'import'])->name('import.import_json');
 | 
			
		||||
    Route::post('preimport', [ImportController::class, 'preimport'])->name('import.preimport');
 | 
			
		||||
 | 
			
		||||
    ;
 | 
			
		||||
    Route::resource('invoices', InvoiceController::class); // name = (invoices. index / create / show / update / destroy / edit
 | 
			
		||||
    Route::get('invoices/{invoice}/delivery_note', [InvoiceController::class, 'deliveryNote'])->name('invoices.delivery_note');
 | 
			
		||||
    Route::get('invoices/{invoice}/{action}', [InvoiceController::class, 'action'])->name('invoices.action');
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ use App\Http\Controllers\Auth\ResetPasswordController;
 | 
			
		||||
use App\Http\Controllers\Bank\NordigenController;
 | 
			
		||||
use App\Http\Controllers\Bank\YodleeController;
 | 
			
		||||
use App\Http\Controllers\BaseController;
 | 
			
		||||
use App\Http\Controllers\ImportQuickbooksController;
 | 
			
		||||
use App\Http\Controllers\ClientPortal\ApplePayDomainController;
 | 
			
		||||
use App\Http\Controllers\Gateways\Checkout3dsController;
 | 
			
		||||
use App\Http\Controllers\Gateways\GoCardlessController;
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,126 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Tests\Feature\Http\Controllers;
 | 
			
		||||
 | 
			
		||||
use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface;
 | 
			
		||||
use App\Services\Import\Quickbooks\Service as QuickbooksService;
 | 
			
		||||
use App\Services\Import\Quickbooks\SdkWrapper as QuickbooksSDK;
 | 
			
		||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
 | 
			
		||||
use Illuminate\Foundation\Testing\RefreshDatabase;
 | 
			
		||||
use Illuminate\Foundation\Testing\WithFaker;
 | 
			
		||||
use Illuminate\Support\Facades\Session;
 | 
			
		||||
use Illuminate\Support\Facades\Cache;
 | 
			
		||||
use Illuminate\Support\Facades\Http;
 | 
			
		||||
use Illuminate\Support\Facades\Bus;
 | 
			
		||||
use GuzzleHttp\Psr7\Message;
 | 
			
		||||
use Illuminate\Support\Arr;
 | 
			
		||||
use Illuminate\Support\Str;
 | 
			
		||||
use Mockery\MockInterface;
 | 
			
		||||
use Tests\MockAccountData;
 | 
			
		||||
use Tests\TestCase;
 | 
			
		||||
use Mockery;
 | 
			
		||||
 | 
			
		||||
class ImportQuickbooksControllerTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    use MockAccountData;
 | 
			
		||||
    use DatabaseTransactions;
 | 
			
		||||
 | 
			
		||||
    private $mock;
 | 
			
		||||
    private $state;
 | 
			
		||||
    
 | 
			
		||||
    protected function setUp(): void {
 | 
			
		||||
 | 
			
		||||
        parent::setUp();
 | 
			
		||||
        
 | 
			
		||||
        $this->state = Str::random(4);
 | 
			
		||||
        $this->mock = Mockery::mock(stdClass::class);
 | 
			
		||||
        $this->makeTestData();
 | 
			
		||||
 | 
			
		||||
        Session::start();
 | 
			
		||||
 | 
			
		||||
        //app()->singleton(QuickbooksInterface::class, fn() => new QuickbooksSDK($this->mock));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testAuthorize(): void
 | 
			
		||||
    {
 | 
			
		||||
       
 | 
			
		||||
        $this->mock->shouldReceive('getState')->andReturn($this->state);
 | 
			
		||||
        $this->mock->shouldReceive('getAuthorizationCodeURL')->andReturn('https://example.com');
 | 
			
		||||
        $this->mock->shouldReceive("getOAuth2LoginHelper")->andReturn($this->mock);
 | 
			
		||||
 | 
			
		||||
        Cache::spy();
 | 
			
		||||
        Cache::shouldReceive('get')
 | 
			
		||||
                    ->with($token  = $this->company->company_key)
 | 
			
		||||
                    ->andReturn( ['company_key' => $token, 'id' => $this->company->id]);
 | 
			
		||||
        Cache::shouldReceive('has')
 | 
			
		||||
                    ->andReturn(true);
 | 
			
		||||
        // Perform the test
 | 
			
		||||
        $response = $this->get(route('authorize.quickbooks', ['token' => $token]));
 | 
			
		||||
        $response->assertStatus(302);
 | 
			
		||||
 | 
			
		||||
        Cache::shouldHaveReceived('put')->once()->with($this->state, $token, 90);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testOnAuthorized(): void
 | 
			
		||||
    {
 | 
			
		||||
        $token = ['company_key' => $this->company->company_key, 'id' => $this->company->id] ;
 | 
			
		||||
 | 
			
		||||
        $this->mock->shouldReceive('getAccessToken')->andReturn(Mockery::mock(stdClass::class,function(MockInterface $mock){
 | 
			
		||||
            $mock->shouldReceive('getAccessToken')->andReturn('abcdefg');
 | 
			
		||||
            $mock->shouldReceive('getRefreshToken')->andReturn('abcdefghi');
 | 
			
		||||
            $mock->shouldReceive('getAccessTokenExpiresAt')->andReturn(3600);
 | 
			
		||||
            $mock->shouldReceive('getRefreshTokenExpiresAt')->andReturn(8726400);
 | 
			
		||||
        }));
 | 
			
		||||
        $this->mock->shouldReceive("getOAuth2LoginHelper")->andReturn($this->mock);
 | 
			
		||||
        $this->mock->shouldReceive('exchangeAuthorizationCodeForToken')->once();
 | 
			
		||||
        
 | 
			
		||||
        Cache::spy();
 | 
			
		||||
        Cache::shouldReceive('has')
 | 
			
		||||
                    ->andReturn(true);
 | 
			
		||||
        Cache::shouldReceive('get')->andReturn($token);
 | 
			
		||||
        Cache::shouldReceive('pull')->andReturn($token['company_key']);
 | 
			
		||||
        // Perform the test
 | 
			
		||||
        $response = $this->get("/quickbooks/authorized/?code=123456&state={$this->state}&realmId=12345678");
 | 
			
		||||
        $response->assertStatus(200);
 | 
			
		||||
 | 
			
		||||
        Cache::shouldHaveReceived('put')->once()->with($token['company_key'], 'abcdefg', 3600);
 | 
			
		||||
 | 
			
		||||
        $this->mock->shouldHaveReceived('exchangeAuthorizationCodeForToken')->once()->with(123456,12345678);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testImport(): void
 | 
			
		||||
    {
 | 
			
		||||
       // Cache::spy();
 | 
			
		||||
        //Bus::fake();
 | 
			
		||||
        $data = $this->setUpTestData('customers');
 | 
			
		||||
        $count = count($data);
 | 
			
		||||
        $this->mock->shouldReceive('Query')->andReturnUsing(
 | 
			
		||||
            function($val, $s = 1, $max = 1000) use ($count, $data) {
 | 
			
		||||
                    if(stristr($val, 'count')) {
 | 
			
		||||
                        return $count;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return Arr::take($data,$max);
 | 
			
		||||
                }
 | 
			
		||||
            );
 | 
			
		||||
        
 | 
			
		||||
        // Perform the test
 | 
			
		||||
        $response = $this->actingAs($this->user)->withHeaders([
 | 
			
		||||
                'X-API-TOKEN' => $this->token,
 | 
			
		||||
            ])->post('/api/v1/import/quickbooks',[
 | 
			
		||||
                'import_types' => ['client']
 | 
			
		||||
            ]);
 | 
			
		||||
        $response->assertStatus(200);
 | 
			
		||||
 | 
			
		||||
        //Cache::shouldHaveReceived('has')->once()->with("{$hash}-client");
 | 
			
		||||
        //Bus::assertDispatched(\App\Jobs\Import\QuickbooksIngest::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function setUpTestData($file) {
 | 
			
		||||
        $data = json_decode(
 | 
			
		||||
            file_get_contents(base_path("tests/Mock/Quickbooks/Data/$file.json")),true
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        return $data;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										139
									
								
								tests/Feature/Import/Quickbooks/QuickbooksTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								tests/Feature/Import/Quickbooks/QuickbooksTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,139 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Tests\Feature\Import\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use Tests\TestCase;
 | 
			
		||||
use App\Import\Providers\Quickbooks;
 | 
			
		||||
use App\Import\Transformer\BaseTransformer;
 | 
			
		||||
use App\Utils\Traits\MakesHash;
 | 
			
		||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
 | 
			
		||||
use Illuminate\Routing\Middleware\ThrottleRequests;
 | 
			
		||||
use Tests\MockAccountData;
 | 
			
		||||
use Illuminate\Support\Facades\Cache;
 | 
			
		||||
use Mockery;
 | 
			
		||||
use App\Models\Client;
 | 
			
		||||
use App\Models\Product;
 | 
			
		||||
use App\Models\Invoice;
 | 
			
		||||
use Illuminate\Support\Str;
 | 
			
		||||
use ReflectionClass;
 | 
			
		||||
use Illuminate\Support\Facades\Auth;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class QuickbooksTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    use MakesHash;
 | 
			
		||||
    use MockAccountData;
 | 
			
		||||
    use DatabaseTransactions;
 | 
			
		||||
 | 
			
		||||
    protected $quickbooks;
 | 
			
		||||
    protected $data;
 | 
			
		||||
 | 
			
		||||
    protected function setUp(): void
 | 
			
		||||
    {
 | 
			
		||||
        parent::setUp();
 | 
			
		||||
        
 | 
			
		||||
        $this->withoutMiddleware(ThrottleRequests::class);
 | 
			
		||||
        config(['database.default' => config('ninja.db.default')]);
 | 
			
		||||
        $this->makeTestData();
 | 
			
		||||
        // 
 | 
			
		||||
        $this->withoutExceptionHandling();
 | 
			
		||||
        Auth::setUser($this->user);
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testImportCallsGetDataOnceForClient()
 | 
			
		||||
    {
 | 
			
		||||
        $data = (json_decode( file_get_contents( base_path('tests/Feature/Import/customers.json') ), true))['Customer'];
 | 
			
		||||
        $hash = Str::random(32);
 | 
			
		||||
        Cache::put($hash.'-client', base64_encode(json_encode($data)), 360);
 | 
			
		||||
 | 
			
		||||
        $quickbooks = Mockery::mock(Quickbooks::class,[[
 | 
			
		||||
            'hash' => $hash,
 | 
			
		||||
            'column_map' => ['client' => ['mapping' => []]],
 | 
			
		||||
            'skip_header' => true,
 | 
			
		||||
            'import_type' => 'quickbooks',
 | 
			
		||||
        ], $this->company ])->makePartial();
 | 
			
		||||
        $quickbooks->shouldReceive('getData')
 | 
			
		||||
            ->once()
 | 
			
		||||
            ->with('client')
 | 
			
		||||
            ->andReturn($data);
 | 
			
		||||
 | 
			
		||||
        // Mocking the dependencies used within the client method
 | 
			
		||||
       
 | 
			
		||||
        $quickbooks->import('client');
 | 
			
		||||
 | 
			
		||||
        $this->assertArrayHasKey('clients', $quickbooks->entity_count);
 | 
			
		||||
        $this->assertGreaterThan(0, $quickbooks->entity_count['clients']);
 | 
			
		||||
 | 
			
		||||
        $base_transformer = new BaseTransformer($this->company);
 | 
			
		||||
        $this->assertTrue($base_transformer->hasClient('Sonnenschein Family Store'));
 | 
			
		||||
        $contact = $base_transformer->getClient('Amy\'s Bird Sanctuary','');
 | 
			
		||||
        $contact = Client::where('name','Amy\'s Bird Sanctuary')->first();
 | 
			
		||||
        $this->assertEquals('(650) 555-3311',$contact->phone);
 | 
			
		||||
        $this->assertEquals('Birds@Intuit.com',$contact->contacts()->first()->email);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testImportCallsGetDataOnceForProducts()
 | 
			
		||||
    {
 | 
			
		||||
        $data = (json_decode( file_get_contents( base_path('tests/Feature/Import/items.json') ), true))['Item'];
 | 
			
		||||
        $hash = Str::random(32);
 | 
			
		||||
        Cache::put($hash.'-item', base64_encode(json_encode($data)), 360);
 | 
			
		||||
 | 
			
		||||
        $quickbooks = Mockery::mock(Quickbooks::class,[[
 | 
			
		||||
            'hash' => $hash,
 | 
			
		||||
            'column_map' => ['item' => ['mapping' => []]],
 | 
			
		||||
            'skip_header' => true,
 | 
			
		||||
            'import_type' => 'quickbooks',
 | 
			
		||||
        ], $this->company ])->makePartial();
 | 
			
		||||
        $quickbooks->shouldReceive('getData')
 | 
			
		||||
            ->once()
 | 
			
		||||
            ->with('product')
 | 
			
		||||
            ->andReturn($data);
 | 
			
		||||
 | 
			
		||||
        // Mocking the dependencies used within the client method
 | 
			
		||||
       
 | 
			
		||||
        $quickbooks->import('product');
 | 
			
		||||
 | 
			
		||||
        $this->assertArrayHasKey('products', $quickbooks->entity_count);
 | 
			
		||||
        $this->assertGreaterThan(0, $quickbooks->entity_count['products']);
 | 
			
		||||
 | 
			
		||||
        $base_transformer = new BaseTransformer($this->company);
 | 
			
		||||
        $this->assertTrue($base_transformer->hasProduct('Gardening'));
 | 
			
		||||
        $product = Product::where('product_key','Pest Control')->first();
 | 
			
		||||
        $this->assertGreaterThanOrEqual( 35, $product->price);
 | 
			
		||||
        $this->assertLessThanOrEqual(0, $product->quantity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testImportCallsGetDataOnceForInvoices()
 | 
			
		||||
    {
 | 
			
		||||
        $data = (json_decode( file_get_contents( base_path('tests/Feature/Import/invoices.json') ), true))['Invoice'];
 | 
			
		||||
        $hash = Str::random(32);
 | 
			
		||||
        Cache::put($hash.'-invoice', base64_encode(json_encode($data)), 360);
 | 
			
		||||
        $quickbooks = Mockery::mock(Quickbooks::class,[[
 | 
			
		||||
            'hash' => $hash,
 | 
			
		||||
            'column_map' => ['invoice' => ['mapping' => []]],
 | 
			
		||||
            'skip_header' => true,
 | 
			
		||||
            'import_type' => 'quickbooks',
 | 
			
		||||
        ], $this->company ])->makePartial();
 | 
			
		||||
        $quickbooks->shouldReceive('getData')
 | 
			
		||||
            ->once()
 | 
			
		||||
            ->with('invoice')
 | 
			
		||||
            ->andReturn($data);
 | 
			
		||||
        $quickbooks->import('invoice');
 | 
			
		||||
        $this->assertArrayHasKey('invoices', $quickbooks->entity_count);
 | 
			
		||||
        $this->assertGreaterThan(0, $quickbooks->entity_count['invoices']);
 | 
			
		||||
        $base_transformer = new BaseTransformer($this->company);
 | 
			
		||||
        $this->assertTrue($base_transformer->hasInvoice(1007));
 | 
			
		||||
        $invoice = Invoice::where('number',1012)->first();
 | 
			
		||||
        $data = collect($data)->where('DocNumber','1012')->first();
 | 
			
		||||
        $this->assertGreaterThanOrEqual( $data['TotalAmt'], $invoice->amount);
 | 
			
		||||
        $this->assertEquals( count($data['Line']) - 1 , count((array)$invoice->line_items));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    protected function tearDown(): void
 | 
			
		||||
    {
 | 
			
		||||
        Mockery::close();
 | 
			
		||||
        parent::tearDown();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1602
									
								
								tests/Feature/Import/customers.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1602
									
								
								tests/Feature/Import/customers.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										4101
									
								
								tests/Feature/Import/invoices.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4101
									
								
								tests/Feature/Import/invoices.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										204
									
								
								tests/Feature/Import/items.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								tests/Feature/Import/items.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,204 @@
 | 
			
		||||
{
 | 
			
		||||
    "Item": [
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Concrete",
 | 
			
		||||
       "Description": "Concrete for fountain installation",
 | 
			
		||||
       "UnitPrice": 0,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "3",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-16T10:36:03-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-19T12:47:47-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Design",
 | 
			
		||||
       "Description": "Custom Design",
 | 
			
		||||
       "UnitPrice": 75,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "4",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-16T10:41:38-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-16T10:41:38-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Gardening",
 | 
			
		||||
       "Description": "Weekly Gardening Service",
 | 
			
		||||
       "UnitPrice": 0,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "6",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-16T10:43:14-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-16T10:43:14-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Hours",
 | 
			
		||||
       "UnitPrice": 0,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "2",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-11T14:42:05-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-11T14:42:05-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Installation",
 | 
			
		||||
       "Description": "Installation of landscape design",
 | 
			
		||||
       "UnitPrice": 50,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "7",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-16T10:43:54-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-16T10:43:54-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Lighting",
 | 
			
		||||
       "Description": "Garden Lighting",
 | 
			
		||||
       "UnitPrice": 0,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "8",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-16T10:44:40-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-19T12:47:38-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Maintenance & Repair",
 | 
			
		||||
       "Description": "Maintenance & Repair",
 | 
			
		||||
       "UnitPrice": 0,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "9",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-16T10:45:18-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-16T10:45:18-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Pest Control",
 | 
			
		||||
       "Description": "Pest Control Services",
 | 
			
		||||
       "UnitPrice": 35,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "10",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-16T10:45:49-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-16T10:45:49-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Pump",
 | 
			
		||||
       "Description": "Fountain Pump",
 | 
			
		||||
       "UnitPrice": 15,
 | 
			
		||||
       "QtyOnHand": 25,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "11",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-16T10:46:45-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-19T13:16:17-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Refunds & Allowances",
 | 
			
		||||
       "Description": "Income due to refunds or allowances",
 | 
			
		||||
       "UnitPrice": 0,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "12",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-16T10:49:18-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-16T10:49:18-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Rock Fountain",
 | 
			
		||||
       "Description": "Rock Fountain",
 | 
			
		||||
       "UnitPrice": 275,
 | 
			
		||||
       "QtyOnHand": 2,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "5",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-16T10:42:19-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-19T13:16:17-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Rocks",
 | 
			
		||||
       "Description": "Garden Rocks",
 | 
			
		||||
       "UnitPrice": 0,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "13",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-16T10:50:11-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-19T12:47:31-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Services",
 | 
			
		||||
       "UnitPrice": 0,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "1",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-11T14:42:05-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-11T14:42:05-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Sod",
 | 
			
		||||
       "Description": "Sod",
 | 
			
		||||
       "UnitPrice": 0,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "14",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-16T10:50:45-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-19T12:47:22-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Soil",
 | 
			
		||||
       "Description": "2 cubic ft. bag",
 | 
			
		||||
       "UnitPrice": 10,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "15",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-16T10:51:28-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-19T12:47:25-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Sprinkler Heads",
 | 
			
		||||
       "Description": "Sprinkler Heads",
 | 
			
		||||
       "UnitPrice": 2,
 | 
			
		||||
       "QtyOnHand": 25,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "16",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-16T10:51:50-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-19T12:51:47-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Sprinkler Pipes",
 | 
			
		||||
       "Description": "Sprinkler Pipes",
 | 
			
		||||
       "UnitPrice": 4,
 | 
			
		||||
       "QtyOnHand": 31,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "17",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-16T10:52:07-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-19T12:57:24-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
       "Name": "Trimming",
 | 
			
		||||
       "Description": "Tree and Shrub Trimming",
 | 
			
		||||
       "UnitPrice": 35,
 | 
			
		||||
       "sparse": true,
 | 
			
		||||
       "Id": "18",
 | 
			
		||||
       "MetaData": {
 | 
			
		||||
        "CreateTime": "2024-06-16T10:52:42-07:00",
 | 
			
		||||
        "LastUpdatedTime": "2024-06-16T10:52:42-07:00"
 | 
			
		||||
       }
 | 
			
		||||
      }
 | 
			
		||||
     ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								tests/Feature/Jobs/Import/QuickbooksIngestTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								tests/Feature/Jobs/Import/QuickbooksIngestTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,53 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Tests\Feature\Jobs\Import;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
 | 
			
		||||
use Illuminate\Foundation\Testing\RefreshDatabase;
 | 
			
		||||
use Illuminate\Foundation\Testing\WithFaker;
 | 
			
		||||
use Illuminate\Support\Facades\Cache;
 | 
			
		||||
use App\Jobs\Import\QuickbooksIngest;
 | 
			
		||||
use Illuminate\Support\Facades\Auth;
 | 
			
		||||
use App\Utils\Traits\MakesHash;
 | 
			
		||||
use Illuminate\Support\Str;
 | 
			
		||||
use Tests\MockAccountData;
 | 
			
		||||
use App\Models\Client;
 | 
			
		||||
use ReflectionClass;
 | 
			
		||||
use Tests\TestCase;
 | 
			
		||||
 | 
			
		||||
class QuickbooksIngestTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    use MakesHash;
 | 
			
		||||
    use MockAccountData;
 | 
			
		||||
    use DatabaseTransactions;
 | 
			
		||||
 | 
			
		||||
    protected $quickbooks;
 | 
			
		||||
 | 
			
		||||
    protected function setUp(): void
 | 
			
		||||
    {
 | 
			
		||||
        parent::setUp();
 | 
			
		||||
 | 
			
		||||
        config(['database.default' => config('ninja.db.default')]);
 | 
			
		||||
        $this->makeTestData();
 | 
			
		||||
        $this->withoutExceptionHandling();
 | 
			
		||||
        Auth::setUser($this->user);
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * A basic feature test example.
 | 
			
		||||
     */
 | 
			
		||||
    public function testCanQuickbooksIngest(): void
 | 
			
		||||
    {
 | 
			
		||||
        $data = (json_decode( file_get_contents( base_path('tests/Feature/Import/customers.json') ), true))['Customer'];
 | 
			
		||||
        $hash = Str::random(32);
 | 
			
		||||
        Cache::put($hash.'-client', base64_encode(json_encode($data)), 360);
 | 
			
		||||
        QuickbooksIngest::dispatch([
 | 
			
		||||
            'hash' => $hash,
 | 
			
		||||
            'column_map' => ['client' => ['mapping' => []]],
 | 
			
		||||
            'skip_header' => true,
 | 
			
		||||
            'import_types' => ['client'],
 | 
			
		||||
        ], $this->company )->handle();
 | 
			
		||||
        $this->assertTrue(Client::withTrashed()->where(['company_id' => $this->company->id, 'name' => "Freeman Sporting Goods"])->exists());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,48 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Tests\Integration\Services\Import\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface;
 | 
			
		||||
use App\Services\Import\Quickbooks\Service as QuickbooksService;
 | 
			
		||||
use App\Services\Import\Quickbooks\SdkWrapper as QuickbooksSDK;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Illuminate\Support\Arr;
 | 
			
		||||
use Tests\TestCase;
 | 
			
		||||
use Mockery;
 | 
			
		||||
 | 
			
		||||
class QuickBooksServiceTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    private $service;
 | 
			
		||||
 | 
			
		||||
    protected function setUp(): void
 | 
			
		||||
    {
 | 
			
		||||
        parent::setUp();
 | 
			
		||||
 
 | 
			
		||||
        $data = json_decode(
 | 
			
		||||
            file_get_contents(base_path('tests/Mock/Quickbooks/Data/customers.json')),true
 | 
			
		||||
        );
 | 
			
		||||
        $count = count($data);
 | 
			
		||||
        $sdkMock = Mockery::mock(sdtClass::class);
 | 
			
		||||
        $sdkMock->shouldReceive('Query')->andReturnUsing(function($val) use ($count, $data) {
 | 
			
		||||
            if(stristr($val, 'count')) {
 | 
			
		||||
                return $count;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Arr::take($data,4);
 | 
			
		||||
        });
 | 
			
		||||
        app()->singleton(QuickbooksInterface::class, fn() => new QuickbooksSDK($sdkMock));
 | 
			
		||||
 | 
			
		||||
        $this->service = app(QuickbooksService::class);
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testImportCustomers()
 | 
			
		||||
    {
 | 
			
		||||
        $collection = $this->service->fetchCustomers(4);
 | 
			
		||||
        
 | 
			
		||||
        $this->assertInstanceOf(Collection::class, $collection);
 | 
			
		||||
        $this->assertEquals(4, $collection->count());
 | 
			
		||||
        $this->assertNotNull($item = $collection->whereStrict('CompanyName', "Cool Cars")->first());
 | 
			
		||||
        $this->assertEquals("Grace", $item['GivenName']);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								tests/Mock/Quickbooks/Data/customer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								tests/Mock/Quickbooks/Data/customer.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
			
		||||
 | 
			
		||||
{
 | 
			
		||||
    "Customer":{
 | 
			
		||||
      "Taxable": false,
 | 
			
		||||
      "BillAddr": {
 | 
			
		||||
       "Id": "4",
 | 
			
		||||
       "Line1": "65 Ocean Dr.",
 | 
			
		||||
       "City": "Half Moon Bay",
 | 
			
		||||
       "CountrySubDivisionCode": "CA",
 | 
			
		||||
       "PostalCode": "94213",
 | 
			
		||||
       "Lat": "37.4300318",
 | 
			
		||||
       "Long": "-122.4336537"
 | 
			
		||||
      },
 | 
			
		||||
      "Job": false,
 | 
			
		||||
      "BillWithParent": false,
 | 
			
		||||
      "Balance": 0,
 | 
			
		||||
      "BalanceWithJobs": 0,
 | 
			
		||||
      "CurrencyRef": {
 | 
			
		||||
       "value": "USD",
 | 
			
		||||
       "name": "United States Dollar"
 | 
			
		||||
      },
 | 
			
		||||
      "PreferredDeliveryMethod": "Print",
 | 
			
		||||
      "IsProject": false,
 | 
			
		||||
      "ClientEntityId": "0",
 | 
			
		||||
      "domain": "QBO",
 | 
			
		||||
      "sparse": false,
 | 
			
		||||
      "Id": "3",
 | 
			
		||||
      "SyncToken": "0",
 | 
			
		||||
      "MetaData": {
 | 
			
		||||
       "CreateTime": "2024-06-11T16:51:22-07:00",
 | 
			
		||||
       "LastUpdatedTime": "2024-06-19T12:59:21-07:00"
 | 
			
		||||
      },
 | 
			
		||||
      "GivenName": "Grace",
 | 
			
		||||
      "FamilyName": "Pariente",
 | 
			
		||||
      "FullyQualifiedName": "Cool Cars",
 | 
			
		||||
      "CompanyName": "Cool Cars",
 | 
			
		||||
      "DisplayName": "Cool Cars",
 | 
			
		||||
      "PrintOnCheckName": "Cool Cars",
 | 
			
		||||
      "Active": true,
 | 
			
		||||
      "V4IDPseudonym": "002098b664cfcba7ac42889139cc9b06d57333",
 | 
			
		||||
      "PrimaryPhone": {
 | 
			
		||||
       "FreeFormNumber": "(415) 555-9933"
 | 
			
		||||
      },
 | 
			
		||||
      "PrimaryEmailAddr": {
 | 
			
		||||
       "Address": "Cool_Cars@intuit.com"
 | 
			
		||||
      }
 | 
			
		||||
     }
 | 
			
		||||
  }
 | 
			
		||||
							
								
								
									
										103
									
								
								tests/Mock/Quickbooks/Data/invoice.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								tests/Mock/Quickbooks/Data/invoice.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,103 @@
 | 
			
		||||
 | 
			
		||||
{
 | 
			
		||||
    "Invoice": {
 | 
			
		||||
      "AllowIPNPayment": false,
 | 
			
		||||
      "AllowOnlinePayment": false,
 | 
			
		||||
      "AllowOnlineCreditCardPayment": false,
 | 
			
		||||
      "AllowOnlineACHPayment": false,
 | 
			
		||||
      "domain": "QBO",
 | 
			
		||||
      "sparse": false,
 | 
			
		||||
      "Id": "34",
 | 
			
		||||
      "SyncToken": "0",
 | 
			
		||||
      "MetaData": {
 | 
			
		||||
       "CreateTime": "2024-06-17T11:09:08-07:00",
 | 
			
		||||
       "LastModifiedByRef": {
 | 
			
		||||
        "value": "9341452725837119"
 | 
			
		||||
       },
 | 
			
		||||
       "LastUpdatedTime": "2024-06-17T11:09:08-07:00"
 | 
			
		||||
      },
 | 
			
		||||
      "CustomField": [],
 | 
			
		||||
      "DocNumber": "1010",
 | 
			
		||||
      "TxnDate": "2024-06-17",
 | 
			
		||||
      "CurrencyRef": {
 | 
			
		||||
       "value": "USD",
 | 
			
		||||
       "name": "United States Dollar"
 | 
			
		||||
      },
 | 
			
		||||
      "LinkedTxn": [],
 | 
			
		||||
      "Line": [
 | 
			
		||||
       {
 | 
			
		||||
        "Id": "1",
 | 
			
		||||
        "LineNum": 1,
 | 
			
		||||
        "Description": "Custom Design",
 | 
			
		||||
        "Amount": 375,
 | 
			
		||||
        "DetailType": "SalesItemLineDetail",
 | 
			
		||||
        "SalesItemLineDetail": {
 | 
			
		||||
         "ItemRef": {
 | 
			
		||||
          "value": "4",
 | 
			
		||||
          "name": "Design"
 | 
			
		||||
         },
 | 
			
		||||
         "UnitPrice": 75,
 | 
			
		||||
         "Qty": 5,
 | 
			
		||||
         "ItemAccountRef": {
 | 
			
		||||
          "value": "82",
 | 
			
		||||
          "name": "Design income"
 | 
			
		||||
         },
 | 
			
		||||
         "TaxCodeRef": {
 | 
			
		||||
          "value": "NON"
 | 
			
		||||
         }
 | 
			
		||||
        }
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "Amount": 375,
 | 
			
		||||
        "DetailType": "SubTotalLineDetail",
 | 
			
		||||
        "SubTotalLineDetail": {}
 | 
			
		||||
       }
 | 
			
		||||
      ],
 | 
			
		||||
      "TxnTaxDetail": {
 | 
			
		||||
       "TotalTax": 0
 | 
			
		||||
      },
 | 
			
		||||
      "CustomerRef": {
 | 
			
		||||
       "value": "29",
 | 
			
		||||
       "name": "Weiskopf Consulting"
 | 
			
		||||
      },
 | 
			
		||||
      "CustomerMemo": {
 | 
			
		||||
       "value": "Thank you for your business and have a great day!"
 | 
			
		||||
      },
 | 
			
		||||
      "BillAddr": {
 | 
			
		||||
       "Id": "56",
 | 
			
		||||
       "Line1": "Nicola Weiskopf",
 | 
			
		||||
       "Line2": "Weiskopf Consulting",
 | 
			
		||||
       "Line3": "45612 Main St.",
 | 
			
		||||
       "Line4": "Bayshore, CA  94326",
 | 
			
		||||
       "Lat": "INVALID",
 | 
			
		||||
       "Long": "INVALID"
 | 
			
		||||
      },
 | 
			
		||||
      "ShipAddr": {
 | 
			
		||||
       "Id": "30",
 | 
			
		||||
       "Line1": "45612 Main St.",
 | 
			
		||||
       "City": "Bayshore",
 | 
			
		||||
       "CountrySubDivisionCode": "CA",
 | 
			
		||||
       "PostalCode": "94326",
 | 
			
		||||
       "Lat": "45.256574",
 | 
			
		||||
       "Long": "-66.0943698"
 | 
			
		||||
      },
 | 
			
		||||
      "FreeFormAddress": true,
 | 
			
		||||
      "SalesTermRef": {
 | 
			
		||||
       "value": "3",
 | 
			
		||||
       "name": "Net 30"
 | 
			
		||||
      },
 | 
			
		||||
      "DueDate": "2024-07-17",
 | 
			
		||||
      "TotalAmt": 375,
 | 
			
		||||
      "ApplyTaxAfterDiscount": false,
 | 
			
		||||
      "PrintStatus": "NotSet",
 | 
			
		||||
      "EmailStatus": "NeedToSend",
 | 
			
		||||
      "BillEmail": {
 | 
			
		||||
       "Address": "Consulting@intuit.com"
 | 
			
		||||
      },
 | 
			
		||||
      "Balance": 375,
 | 
			
		||||
      "DeliveryInfo": {
 | 
			
		||||
       "DeliveryType": "Email"
 | 
			
		||||
      }
 | 
			
		||||
     }, 
 | 
			
		||||
    "time": "2015-07-24T10:48:27.082-07:00"
 | 
			
		||||
  }
 | 
			
		||||
							
								
								
									
										15
									
								
								tests/Mock/Quickbooks/Data/item.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								tests/Mock/Quickbooks/Data/item.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
 | 
			
		||||
{
 | 
			
		||||
    "Item":{
 | 
			
		||||
        "Name": "Pump",
 | 
			
		||||
        "Description": "Fountain Pump",
 | 
			
		||||
        "UnitPrice": 15,
 | 
			
		||||
        "QtyOnHand": 25,
 | 
			
		||||
        "sparse": true,
 | 
			
		||||
        "Id": "11",
 | 
			
		||||
        "MetaData": {
 | 
			
		||||
            "CreateTime": "2024-06-16T10:46:45-07:00",
 | 
			
		||||
            "LastUpdatedTime": "2024-06-19T13:16:17-07:00"
 | 
			
		||||
        }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,21 @@
 | 
			
		||||
Status: 200
 | 
			
		||||
Connection: keep-alive
 | 
			
		||||
Keep-Alive: timeout=5
 | 
			
		||||
Strict-Transport-Security: max-age=15552000
 | 
			
		||||
Cache-Control: no-cache, no-store
 | 
			
		||||
Content-Type: application/json;charset=utf-8
 | 
			
		||||
Server: nginx
 | 
			
		||||
{
 | 
			
		||||
 "refreshToken": "AB11730384727MVrceUmO0gapXJFBT0IkpkI71FCkkWrRAVn8P",
 | 
			
		||||
 "accessToken": "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..vW8l3PbGZGjF5Nbq-ssyKw
 | 
			
		||||
     .mSxu2T4j4OYXa8Gr7z2OU5cqVg19MasGFllKMvT2Jo0wYrovyN6q9OZIwul1iSKI10A3ZIBT5VnsTnW6ZQG
 | 
			
		||||
     -rKcvmPNImZhYyCeBwzAUcnFQDPax5sWqlSHwEEviLPSH6R6Rs0wSNtszD4sEva8Z2pHmMZ4q2VNX3SAJA2R8spLhkItcULW4COdUNhZHl9fk4m7Xo66q8iBmE
 | 
			
		||||
     IV5269DXI-x8tCe7BdBrQbIabd5tqmD4gllS4vInK__86LLvESHDuY51eQEJt8eLRhSFIK7ZT2zoS6JKp1TfuvEg7HULkP53u12hSZsTETCfFneofOXhqEAQa8
 | 
			
		||||
     SgqiYV-8_I4Mm7P_TDeRX06CdPMMwH9Wrvmmmihhk8TNz6MynEn4Cpjf_iAedtyhXnELTnefTUC8_
 | 
			
		||||
     --w9_5FCCHyHgS45A2289mhIY1eLH_i8gjzuGKs7zYOaTvm1nQ_Jt1Z1Guy-jteNwt6_2OoMYfQaMss8AwHAZr7c
 | 
			
		||||
     -bhvtBY0Qi0VoqfMUdicuxMXd3HZCXUuKqXpGn5TXwJ6T6flf8Slgh2GIKUhMg1IoXMFKD3IVaPU81qyaGbuBIhwVdLywgY0guAlnYYUAHX4n_pmDf6zGxGQ0V
 | 
			
		||||
     RXMF2iQY1Q21OKGUqgkS8xaQajV5KERVZkp4thEurLg4EAxDdT71VxOjU5IszIRPfg8bw_X5g73pTNyF
 | 
			
		||||
     -3TRCJHe_FXz7P_Ee3Py_9O1Bw7MtbKxiwlSmumOOBKHAtorQhakw.nYNzTumdNDhpqLwCIkJNlw",
 | 
			
		||||
 "expires_in": 3600,
 | 
			
		||||
 "x_refresh_token_expires_in": 8726400
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1617
									
								
								tests/Mock/Quickbooks/Http/Response/200-cutomer-response.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1617
									
								
								tests/Mock/Quickbooks/Http/Response/200-cutomer-response.txt
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										4114
									
								
								tests/Mock/Quickbooks/Http/Response/200-invoice-response.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4114
									
								
								tests/Mock/Quickbooks/Http/Response/200-invoice-response.txt
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
Status: 200
 | 
			
		||||
Connection: keep-alive
 | 
			
		||||
Keep-Alive: timeout=5
 | 
			
		||||
Strict-Transport-Security: max-age=15552000
 | 
			
		||||
Cache-Control: no-cache, no-store
 | 
			
		||||
Content-Type: application/json;charset=utf-8
 | 
			
		||||
Server: nginx
 | 
			
		||||
{
 | 
			
		||||
 "refreshToken": "AB11730389456hovpbWZFrn4st1d0qkEdnL9g3N0TNWERGeJvg",
 | 
			
		||||
 "accessToken": "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..JKD1fuk8W29Agq8x-CF3FQ
 | 
			
		||||
     .ioAPe43etRVWLT3GUYdF9yS_mqYqDsIkOE3Y69g7i5tj1N0mHkdxoTpfAoP59Q5PtAzndTWHjFEIF_z2mMkyuTUAJqTvrMjyRM
 | 
			
		||||
     -AymqFpPTly7UUs_lWZCCta5RRT8uEifeUT5VcJNT3_1nbNL85mHpXbkW-Ficovp0tR4E52BPjdrSfFbAkVQQoZnHe13rJWLgj9L
 | 
			
		||||
     -R63UzwlktE3lF7sgaGg5Gdsu41b8ATa0Jp2LZ4lT3QnPVcbhx8awWlwJ8qW-hElkmeBKPLju2xR1JIhw7bpDtV1BAOBkEjs7TL5oKKiUxV-slZQBhmf8cTJT
 | 
			
		||||
     -p6RqtOCyV-mlAL7kixc7CGWtuqwnb6j_HckdIwV8CX7Wivvt-Gnl80v0qjUQsacwighkm0KAYMoM__eTi8DM-xdvskzoLfPzblImxwKte
 | 
			
		||||
     -31tBg_o9v9oE6ZHr0D-yllGv58Cu5vdsFajEEo7-MTFnxpxihd8_OmXSdegGF3lLA2lMk7qlkrq6
 | 
			
		||||
     -Uw46rOfv4LkBo2C0OIYN1c5Wnt6zf7461BM5NerMEzeHrgjFDiTBamdWeXQfBOZAX1KJ1IoM_RJrpCParJqTx1Ia0bDn_fcT5ysTEUO9c811GWYpMhyVCq
 | 
			
		||||
     -qIzz4JrdIx7F7xVc4dLffVL2PHibC-cXtWcx85j1KzPOr0YEAFGoawyCH6uvMuubJlisc_BSDa_SH_gDyTtMhzeOW
 | 
			
		||||
     -zQi0HXveOjtQPbIa91K9ddhcilB1LSDO4Qg.fxg-wLi7mDiHgi6YTgm36Q",
 | 
			
		||||
 "expires_in": 3600,
 | 
			
		||||
 "x_refresh_token_expires_in": 8726345
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,50 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Tests\Unit\Import\Transformer\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use Tests\TestCase;
 | 
			
		||||
use App\Import\Transformer\Quickbooks\ClientTransformer;
 | 
			
		||||
 | 
			
		||||
class ClientTransformerTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    private $customer_data;
 | 
			
		||||
    private $tranformed_data;
 | 
			
		||||
    private $transformer;
 | 
			
		||||
 | 
			
		||||
    protected function setUp(): void
 | 
			
		||||
    {
 | 
			
		||||
        parent::setUp();
 | 
			
		||||
        // Mock the company object
 | 
			
		||||
        $company = (new \App\Factory\CompanyFactory)->create(1234);
 | 
			
		||||
 | 
			
		||||
        // Read the JSON string from a file and decode into an associative array
 | 
			
		||||
        $this->customer_data = json_decode( file_get_contents( app_path('/../tests/Mock/Quickbooks/Data/customer.json') ), true);
 | 
			
		||||
        $this->transformer = new ClientTransformer($company);
 | 
			
		||||
        $this->transformed_data = $this->transformer->transform($this->customer_data['Customer']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testClassExists()
 | 
			
		||||
    {
 | 
			
		||||
        $this->assertInstanceOf(ClientTransformer::class, $this->transformer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testTransformReturnsArray()
 | 
			
		||||
    {
 | 
			
		||||
        $this->assertIsArray($this->transformed_data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testTransformHasNameProperty()
 | 
			
		||||
    {
 | 
			
		||||
        $this->assertArrayHasKey('name', $this->transformed_data);
 | 
			
		||||
        $this->assertEquals($this->customer_data['Customer']['CompanyName'], $this->transformed_data['name']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testTransformHasContactsProperty()
 | 
			
		||||
    {
 | 
			
		||||
       $this->assertArrayHasKey('contacts', $this->transformed_data);
 | 
			
		||||
        $this->assertIsArray($this->transformed_data['contacts']);
 | 
			
		||||
        $this->assertArrayHasKey(0, $this->transformed_data['contacts']);
 | 
			
		||||
        $this->assertArrayHasKey('email', $this->transformed_data['contacts'][0]);
 | 
			
		||||
        $this->assertEquals($this->customer_data['Customer']['PrimaryEmailAddr']['Address'], $this->transformed_data['contacts'][0]['email']);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,75 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Tests\Unit\Import\Transformer\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use Tests\TestCase;
 | 
			
		||||
use Tests\MockAccountData;
 | 
			
		||||
use Illuminate\Support\Facades\Auth;
 | 
			
		||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
 | 
			
		||||
use App\Import\Transformer\Quickbooks\InvoiceTransformer;
 | 
			
		||||
 | 
			
		||||
class InvoiceTransformerTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    use MockAccountData;
 | 
			
		||||
    use DatabaseTransactions;
 | 
			
		||||
 | 
			
		||||
    private $invoiceData;
 | 
			
		||||
    private $tranformedData;
 | 
			
		||||
    private $transformer;
 | 
			
		||||
 | 
			
		||||
    protected function setUp(): void
 | 
			
		||||
    {
 | 
			
		||||
        parent::setUp();
 | 
			
		||||
 | 
			
		||||
        $this->makeTestData();
 | 
			
		||||
        $this->withoutExceptionHandling();
 | 
			
		||||
        Auth::setUser($this->user);
 | 
			
		||||
        // Read the JSON string from a file and decode into an associative array
 | 
			
		||||
        $this->invoiceData = json_decode( file_get_contents( app_path('/../tests/Mock/Quickbooks/Data/invoice.json') ), true);
 | 
			
		||||
        $this->transformer = new InvoiceTransformer($this->company);
 | 
			
		||||
        $this->transformedData = $this->transformer->transform($this->invoiceData['Invoice']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testIsInstanceOf()
 | 
			
		||||
    {
 | 
			
		||||
        $this->assertInstanceOf(InvoiceTransformer::class, $this->transformer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testTransformReturnsArray()
 | 
			
		||||
    {
 | 
			
		||||
       $this->assertIsArray($this->transformedData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testTransformContainsNumber()
 | 
			
		||||
    {
 | 
			
		||||
        $this->assertArrayHasKey('number', $this->transformedData);
 | 
			
		||||
        $this->assertEquals($this->invoiceData['Invoice']['DocNumber'], $this->transformedData['number']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testTransformContainsDueDate()
 | 
			
		||||
    {
 | 
			
		||||
        $this->assertArrayHasKey('due_date', $this->transformedData);
 | 
			
		||||
        $this->assertEquals(strtotime($this->invoiceData['Invoice']['DueDate']), strtotime($this->transformedData['due_date']));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testTransformContainsAmount()
 | 
			
		||||
    {
 | 
			
		||||
        $this->assertArrayHasKey('amount', $this->transformedData);
 | 
			
		||||
        $this->assertIsFloat($this->transformedData['amount']);
 | 
			
		||||
        $this->assertEquals($this->invoiceData['Invoice']['TotalAmt'], $this->transformedData['amount']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testTransformContainsLineItems()
 | 
			
		||||
    {
 | 
			
		||||
        $this->assertArrayHasKey('line_items', $this->transformedData);
 | 
			
		||||
        $this->assertNotNull($this->transformedData['line_items']);
 | 
			
		||||
        $this->assertEquals( count($this->invoiceData['Invoice']["Line"]) - 1, count($this->transformedData['line_items']) );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testTransformHasClient()
 | 
			
		||||
    {
 | 
			
		||||
        $this->assertArrayHasKey('client', $this->transformedData);
 | 
			
		||||
        $this->assertArrayHasKey('contacts', $this->transformedData['client']);
 | 
			
		||||
        $this->assertEquals($this->invoiceData['Invoice']['BillEmail']['Address'], $this->transformedData['client']['contacts'][0]['email']);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,44 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Tests\Unit\Import\Transformer\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use Tests\TestCase;
 | 
			
		||||
use App\Import\Transformer\Quickbooks\ProductTransformer;
 | 
			
		||||
 | 
			
		||||
class ProductTransformerTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    private $product_data;
 | 
			
		||||
    private $tranformed_data;
 | 
			
		||||
    private $transformer;
 | 
			
		||||
 | 
			
		||||
    protected function setUp(): void
 | 
			
		||||
    {
 | 
			
		||||
        parent::setUp();
 | 
			
		||||
        // Mock the company object
 | 
			
		||||
        $company = (new \App\Factory\CompanyFactory)->create(1234);
 | 
			
		||||
 | 
			
		||||
        // Read the JSON string from a file and decode into an associative array
 | 
			
		||||
        $this->product_data = json_decode( file_get_contents( app_path('/../tests/Mock/Quickbooks/Data/item.json') ), true);
 | 
			
		||||
        $this->transformer = new ProductTransformer($company);
 | 
			
		||||
        $this->transformed_data = $this->transformer->transform($this->product_data['Item']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testClassExists()
 | 
			
		||||
    {
 | 
			
		||||
        $this->assertInstanceOf(ProductTransformer::class, $this->transformer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testTransformReturnsArray()
 | 
			
		||||
    {
 | 
			
		||||
        $this->assertIsArray($this->transformed_data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testTransformHasProperties()
 | 
			
		||||
    {
 | 
			
		||||
        $this->assertArrayHasKey('product_key', $this->transformed_data);
 | 
			
		||||
        $this->assertArrayHasKey('price', $this->transformed_data);
 | 
			
		||||
        $this->assertTrue(is_numeric($this->transformed_data['price']));
 | 
			
		||||
        $this->assertEquals(15, (int) $this->transformed_data['price'] );
 | 
			
		||||
        $this->assertEquals((int) $this->product_data['Item']['QtyOnHand'], $this->transformed_data['quantity']);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								tests/Unit/Services/Import/Quickbooks/SdkWrapperTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								tests/Unit/Services/Import/Quickbooks/SdkWrapperTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,47 @@
 | 
			
		||||
<?php
 | 
			
		||||
// tests/Unit/IntuitSDKWrapperTest.php
 | 
			
		||||
namespace Tests\Unit\Services\Import\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use Mockery;
 | 
			
		||||
use Tests\TestCase;
 | 
			
		||||
use Illuminate\Support\Arr;
 | 
			
		||||
use App\Services\Import\Quickbooks\Contracts\SdkInterface;
 | 
			
		||||
use App\Services\Import\Quickbooks\SdkWrapper as QuickbookSDK;
 | 
			
		||||
 | 
			
		||||
class SdkWrapperTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    protected $sdk;
 | 
			
		||||
    protected $sdkMock;
 | 
			
		||||
 | 
			
		||||
    protected function setUp(): void
 | 
			
		||||
    {
 | 
			
		||||
        parent::setUp();
 | 
			
		||||
 | 
			
		||||
        $this->sdkMock = Mockery::mock(sdtClass::class);
 | 
			
		||||
        $this->sdk = new QuickbookSDK($this->sdkMock);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function testIsInstanceOf() {
 | 
			
		||||
        $this->assertInstanceOf(SdkInterface::class, $this->sdk);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function testMethodFetchRecords() {
 | 
			
		||||
        $data = json_decode(
 | 
			
		||||
                    file_get_contents(base_path('tests/Mock/Quickbooks/Data/customers.json')),true
 | 
			
		||||
        );
 | 
			
		||||
        $count = count($data);
 | 
			
		||||
        $this->sdkMock->shouldReceive('Query')->andReturnUsing(function($val) use ($count, $data) {
 | 
			
		||||
            if(stristr($val, 'count')) {
 | 
			
		||||
                return $count;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Arr::take($data,4);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        $this->assertEquals($count, $this->sdk->totalRecords('Customer'));
 | 
			
		||||
        $this->assertEquals(4, count($this->sdk->fetchRecords('Customer', 4)));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								tests/Unit/Services/Import/Quickbooks/ServiceTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								tests/Unit/Services/Import/Quickbooks/ServiceTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace Tests\Unit\Services\Import\Quickbooks;
 | 
			
		||||
 | 
			
		||||
use Mockery;
 | 
			
		||||
use Tests\TestCase;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use App\Services\Import\Quickbooks\Service as QuickbooksService;
 | 
			
		||||
use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface;
 | 
			
		||||
 | 
			
		||||
class ServiceTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    protected $service;
 | 
			
		||||
 | 
			
		||||
    protected function setUp(): void
 | 
			
		||||
    {
 | 
			
		||||
        parent::setUp();
 | 
			
		||||
        // Inject the mock into the IntuitSDKservice instance
 | 
			
		||||
        $this->service = Mockery::mock( new QuickbooksService(Mockery::mock(QuickbooksInterface::class)))->shouldAllowMockingProtectedMethods();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function tearDown(): void
 | 
			
		||||
    {
 | 
			
		||||
        Mockery::close();
 | 
			
		||||
        parent::tearDown();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testTotalRecords()
 | 
			
		||||
    {
 | 
			
		||||
        $entity = 'Customer';
 | 
			
		||||
        $count = 10;
 | 
			
		||||
 | 
			
		||||
        $this->service->shouldReceive('totalRecords')
 | 
			
		||||
                      ->with($entity)
 | 
			
		||||
                      ->andReturn($count);
 | 
			
		||||
 | 
			
		||||
        $result = $this->service->totalRecords($entity);
 | 
			
		||||
 | 
			
		||||
        $this->assertEquals($count, $result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testHasFetchRecords()
 | 
			
		||||
    {
 | 
			
		||||
        $entity = 'Customer';
 | 
			
		||||
        $count = 10;
 | 
			
		||||
 | 
			
		||||
        $this->service->shouldReceive('fetchRecords')
 | 
			
		||||
                      ->with($entity, $count)
 | 
			
		||||
                      ->andReturn(collect());
 | 
			
		||||
 | 
			
		||||
        $result = $this->service->fetchCustomers($count);
 | 
			
		||||
 | 
			
		||||
        $this->assertInstanceOf(Collection::class, $result);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user