mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-10-31 13:27:33 -04:00 
			
		
		
		
	Merge branch 'v5-develop' into v5-stable
This commit is contained in:
		
						commit
						e5595cf914
					
				| @ -61,3 +61,11 @@ SENTRY_LARAVEL_DSN=https://39389664f3f14969b4c43dadda00a40b@sentry2.invoicing.co | ||||
| 
 | ||||
| GOOGLE_PLAY_PACKAGE_NAME= | ||||
| APPSTORE_PASSWORD= | ||||
| 
 | ||||
| MICROSOFT_CLIENT_ID= | ||||
| MICROSOFT_CLIENT_SECRET= | ||||
| MICROSOFT_REDIRECT_URI= | ||||
| 
 | ||||
| APPLE_CLIENT_ID= | ||||
| APPLE_CLIENT_SECRET= | ||||
| APPLE_REDIRECT_URI= | ||||
|  | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,6 +1,7 @@ | ||||
| /node_modules | ||||
| /public/hot | ||||
| /public/storage | ||||
| /public/react | ||||
| /storage/*.key | ||||
| /vendor | ||||
| /.idea | ||||
|  | ||||
| @ -1 +1 @@ | ||||
| 5.3.98 | ||||
| 5.4.0 | ||||
| @ -62,7 +62,7 @@ class EmailTemplateDefaults | ||||
|             case 'email_template_custom3': | ||||
|                 return self::emailInvoiceTemplate(); | ||||
|             case 'email_template_purchase_order': | ||||
|                 return self::emailPurchaseOrderSubject(); | ||||
|                 return self::emailPurchaseOrderTemplate(); | ||||
|                 break; | ||||
| 
 | ||||
|             /* Subject */ | ||||
| @ -157,7 +157,7 @@ class EmailTemplateDefaults | ||||
| 
 | ||||
|     public static function emailPurchaseOrderSubject() | ||||
|     { | ||||
|         return ctrans('texts.purchase_order_subject', ['number' => '$number']); | ||||
|         return ctrans('texts.purchase_order_subject', ['number' => '$number', 'account' => '$account']); | ||||
|     } | ||||
| 
 | ||||
|     public static function emailPurchaseOrderTemplate() | ||||
|  | ||||
							
								
								
									
										51
									
								
								app/Events/PurchaseOrder/PurchaseOrderWasAccepted.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								app/Events/PurchaseOrder/PurchaseOrderWasAccepted.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | ||||
| <?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\Events\PurchaseOrder; | ||||
| 
 | ||||
| use App\Models\Company; | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Models\PurchaseOrderInvitation; | ||||
| use App\Models\VendorContact; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| /** | ||||
|  * Class PurchaseOrderWasAccepted. | ||||
|  */ | ||||
| class PurchaseOrderWasAccepted | ||||
| { | ||||
|     use SerializesModels; | ||||
| 
 | ||||
|     /** | ||||
|      * @var PurchaseOrder | ||||
|      */ | ||||
|     public $purchase_order; | ||||
| 
 | ||||
|     public $company; | ||||
| 
 | ||||
|     public $event_vars; | ||||
| 
 | ||||
|     public $contact; | ||||
|     /** | ||||
|      * Create a new event instance. | ||||
|      * | ||||
|      * @param PurchaseOrder $purchase_order | ||||
|      * @param Company $company | ||||
|      * @param array $event_vars | ||||
|      */ | ||||
|     public function __construct(PurchaseOrder $purchase_order, VendorContact $contact, Company $company, array $event_vars) | ||||
|     { | ||||
|         $this->purchase_order = $purchase_order; | ||||
|         $this->contact = $contact; | ||||
|         $this->company = $company; | ||||
|         $this->event_vars = $event_vars; | ||||
|     } | ||||
| } | ||||
| @ -13,6 +13,7 @@ namespace App\Events\PurchaseOrder; | ||||
| 
 | ||||
| use App\Models\Company; | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Models\PurchaseOrderInvitation; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| /** | ||||
| @ -25,7 +26,7 @@ class PurchaseOrderWasEmailed | ||||
|     /** | ||||
|      * @var PurchaseOrder | ||||
|      */ | ||||
|     public $purchase_order; | ||||
|     public $invitation; | ||||
| 
 | ||||
|     public $company; | ||||
| 
 | ||||
| @ -38,9 +39,9 @@ class PurchaseOrderWasEmailed | ||||
|      * @param Company $company | ||||
|      * @param array $event_vars | ||||
|      */ | ||||
|     public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars) | ||||
|     public function __construct(PurchaseOrderInvitation $invitation, Company $company, array $event_vars) | ||||
|     { | ||||
|         $this->purchase_order = $purchase_order; | ||||
|         $this->invitation = $invitation; | ||||
|         $this->company = $company; | ||||
|         $this->event_vars = $event_vars; | ||||
|     } | ||||
|  | ||||
| @ -13,6 +13,7 @@ namespace App\Events\PurchaseOrder; | ||||
| 
 | ||||
| use App\Models\Company; | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Models\PurchaseOrderInvitation; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| /** | ||||
| @ -25,7 +26,7 @@ class PurchaseOrderWasViewed | ||||
|     /** | ||||
|      * @var PurchaseOrder | ||||
|      */ | ||||
|     public $purchase_order; | ||||
|     public $invitation; | ||||
| 
 | ||||
|     public $company; | ||||
| 
 | ||||
| @ -38,9 +39,9 @@ class PurchaseOrderWasViewed | ||||
|      * @param Company $company | ||||
|      * @param array $event_vars | ||||
|      */ | ||||
|     public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars) | ||||
|     public function __construct(PurchaseOrderInvitation $invitation, Company $company, array $event_vars) | ||||
|     { | ||||
|         $this->purchase_order = $purchase_order; | ||||
|         $this->invitation = $invitation; | ||||
|         $this->company = $company; | ||||
|         $this->event_vars = $event_vars; | ||||
|     } | ||||
|  | ||||
| @ -222,6 +222,9 @@ class Handler extends ExceptionHandler | ||||
|             case 'user': | ||||
|                 $login = 'login'; | ||||
|                 break; | ||||
|             case 'vendor': | ||||
|                 $login = 'vendor.catchall'; | ||||
|                 break; | ||||
|             default: | ||||
|                 $login = 'default'; | ||||
|                 break; | ||||
|  | ||||
| @ -12,9 +12,11 @@ | ||||
| namespace App\Http\Controllers; | ||||
| 
 | ||||
| use App\Http\Requests\Account\CreateAccountRequest; | ||||
| use App\Http\Requests\Account\UpdateAccountRequest; | ||||
| use App\Jobs\Account\CreateAccount; | ||||
| use App\Models\Account; | ||||
| use App\Models\CompanyUser; | ||||
| use App\Transformers\AccountTransformer; | ||||
| use App\Transformers\CompanyUserTransformer; | ||||
| use App\Utils\TruthSource; | ||||
| use Illuminate\Foundation\Bus\DispatchesJobs; | ||||
| @ -157,4 +159,22 @@ class AccountController extends BaseController | ||||
| 
 | ||||
|         return $this->listResponse($ct); | ||||
|     } | ||||
| 
 | ||||
|     public function update(UpdateAccountRequest $request, Account $account) | ||||
|     { | ||||
| 
 | ||||
|         $fi = new \FilesystemIterator(public_path('react'), \FilesystemIterator::SKIP_DOTS); | ||||
| 
 | ||||
|         if(iterator_count($fi) < 30) | ||||
|             return response()->json(['message' => 'React App Not Installed, Please install the React app before attempting to switch.'], 400); | ||||
| 
 | ||||
|         $account->fill($request->all()); | ||||
|         $account->save(); | ||||
| 
 | ||||
|         $this->entity_type = Account::class; | ||||
| 
 | ||||
|         $this->entity_transformer = AccountTransformer::class; | ||||
| 
 | ||||
|         return $this->itemResponse($account); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -92,7 +92,7 @@ class LoginController extends BaseController | ||||
|      * @return void | ||||
|      * deprecated .1 API ONLY we don't need to set any session variables | ||||
|      */ | ||||
|     public function authenticated(Request $request, User $user) : void | ||||
|     public function authenticated(Request $request, User $user): void | ||||
|     { | ||||
|         //$this->setCurrentCompanyId($user->companies()->first()->account->default_company_id);
 | ||||
|     } | ||||
| @ -168,9 +168,9 @@ class LoginController extends BaseController | ||||
|             $this->fireLockoutEvent($request); | ||||
| 
 | ||||
|             return response() | ||||
|             ->json(['message' => 'Too many login attempts, you are being throttled'], 401) | ||||
|             ->header('X-App-Version', config('ninja.app_version')) | ||||
|             ->header('X-Api-Version', config('ninja.minimum_client_version')); | ||||
|                 ->json(['message' => 'Too many login attempts, you are being throttled'], 401) | ||||
|                 ->header('X-App-Version', config('ninja.app_version')) | ||||
|                 ->header('X-Api-Version', config('ninja.minimum_client_version')); | ||||
|         } | ||||
| 
 | ||||
|         if ($this->attemptLogin($request)) { | ||||
| @ -196,7 +196,7 @@ class LoginController extends BaseController | ||||
| 
 | ||||
|             } | ||||
|             elseif($user->google_2fa_secret && !$request->has('one_time_password')) { | ||||
|                  | ||||
| 
 | ||||
|                     return response() | ||||
|                     ->json(['message' => ctrans('texts.invalid_one_time_password')], 401) | ||||
|                     ->header('X-App-Version', config('ninja.app_version')) | ||||
| @ -234,23 +234,23 @@ class LoginController extends BaseController | ||||
|             // /* Ensure the user has a valid token */
 | ||||
|             // if($user->company_users()->count() != $user->tokens()->count())
 | ||||
|             // {
 | ||||
|                | ||||
| 
 | ||||
|             //   $user->companies->each(function($company) use($user, $request){
 | ||||
|                | ||||
| 
 | ||||
|             //     if(!CompanyToken::where('user_id', $user->id)->where('company_id', $company->id)->exists()){
 | ||||
|                  | ||||
| 
 | ||||
|             //       CreateCompanyToken::dispatchNow($company, $user, $request->server('HTTP_USER_AGENT'));
 | ||||
|                    | ||||
| 
 | ||||
|             //     }
 | ||||
|                | ||||
| 
 | ||||
|             //   });
 | ||||
|                | ||||
| 
 | ||||
|             // }
 | ||||
| 
 | ||||
|             // $truth->setCompanyToken(CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $user->account->default_company->id)->first());
 | ||||
| 
 | ||||
|             /*On the hosted platform, only owners can login for free/pro accounts*/ | ||||
|             if(Ninja::isHosted() && !$cu->first()->is_owner && !$user->account->isEnterpriseClient()) | ||||
|             if (Ninja::isHosted() && !$cu->first()->is_owner && !$user->account->isEnterpriseClient()) | ||||
|                 return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403); | ||||
| 
 | ||||
|             event(new UserLoggedIn($user, $user->account->default_company, Ninja::eventVars($user->id))); | ||||
| @ -267,9 +267,9 @@ class LoginController extends BaseController | ||||
|             $this->incrementLoginAttempts($request); | ||||
| 
 | ||||
|             return response() | ||||
|             ->json(['message' => ctrans('texts.invalid_credentials')], 401) | ||||
|             ->header('X-App-Version', config('ninja.app_version')) | ||||
|             ->header('X-Api-Version', config('ninja.minimum_client_version')); | ||||
|                 ->json(['message' => ctrans('texts.invalid_credentials')], 401) | ||||
|                 ->header('X-App-Version', config('ninja.app_version')) | ||||
|                 ->header('X-Api-Version', config('ninja.minimum_client_version')); | ||||
| 
 | ||||
|         } | ||||
|     } | ||||
| @ -317,29 +317,28 @@ class LoginController extends BaseController | ||||
|     { | ||||
|         $truth = app()->make(TruthSource::class); | ||||
| 
 | ||||
|         if($truth->getCompanyToken()) | ||||
|         if ($truth->getCompanyToken()) | ||||
|             $company_token = $truth->getCompanyToken(); | ||||
|         else | ||||
|             $company_token = CompanyToken::where('token', $request->header('X-API-TOKEN'))->first(); | ||||
| 
 | ||||
|         $cu = CompanyUser::query() | ||||
|                           ->where('user_id', $company_token->user_id); | ||||
|             ->where('user_id', $company_token->user_id); | ||||
| 
 | ||||
|         if($cu->count() == 0) | ||||
|         if ($cu->count() == 0) | ||||
|             return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400); | ||||
| 
 | ||||
|         $cu->first()->account->companies->each(function ($company) use($cu, $request){ | ||||
|         $cu->first()->account->companies->each(function ($company) use ($cu, $request) { | ||||
| 
 | ||||
|             if($company->tokens()->where('is_system', true)->count() == 0) | ||||
|             { | ||||
|             if ($company->tokens()->where('is_system', true)->count() == 0) { | ||||
|                 CreateCompanyToken::dispatchNow($company, $cu->first()->user, $request->server('HTTP_USER_AGENT')); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         if($request->has('current_company') && $request->input('current_company') == 'true') | ||||
|           $cu->where("company_id", $company_token->company_id); | ||||
|         if ($request->has('current_company') && $request->input('current_company') == 'true') | ||||
|             $cu->where("company_id", $company_token->company_id); | ||||
| 
 | ||||
|         if(Ninja::isHosted() && !$cu->first()->is_owner && !$cu->first()->user->account->isEnterpriseClient()) | ||||
|         if (Ninja::isHosted() && !$cu->first()->is_owner && !$cu->first()->user->account->isEnterpriseClient()) | ||||
|             return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403); | ||||
| 
 | ||||
|         return $this->refreshResponse($cu); | ||||
| @ -359,24 +358,134 @@ class LoginController extends BaseController | ||||
|      */ | ||||
|     public function oauthApiLogin() | ||||
|     { | ||||
| 
 | ||||
|         $message = 'Provider not supported'; | ||||
|         if (request()->input('provider') == 'google') { | ||||
|             return $this->handleGoogleOauth(); | ||||
|         } elseif (request()->input('provider') == 'microsoft') { | ||||
|             if (request()->has('token')) { | ||||
|                 return $this->handleSocialiteLogin('microsoft', request()->get('token')); | ||||
|             } else { | ||||
|                 $message = 'Bearer token missing for the microsoft login'; | ||||
|             } | ||||
|         } elseif (request()->input('provider') == 'apple') { | ||||
|             if (request()->has('token')) { | ||||
|                 return $this->handleSocialiteLogin('apple', request()->get('token')); | ||||
|             } else { | ||||
|                 $message = 'Token is missing for the apple login'; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return response() | ||||
|         ->json(['message' => 'Provider not supported'], 400) | ||||
|         ->header('X-App-Version', config('ninja.app_version')) | ||||
|         ->header('X-Api-Version', config('ninja.minimum_client_version')); | ||||
|             ->json(['message' => $message], 400) | ||||
|             ->header('X-App-Version', config('ninja.app_version')) | ||||
|             ->header('X-Api-Version', config('ninja.minimum_client_version')); | ||||
|     } | ||||
| 
 | ||||
|     private function hydrateCompanyUser() :Builder | ||||
|     private function getSocialiteUser(string $provider, string $token) | ||||
|     { | ||||
|         return Socialite::driver($provider)->userFromToken($token); | ||||
|     } | ||||
| 
 | ||||
|     private function handleSocialiteLogin($provider, $token) | ||||
|     { | ||||
|         $user = $this->getSocialiteUser($provider, $token); | ||||
|         if ($user) { | ||||
|             return $this->loginOrCreateFromSocialite($user, $provider); | ||||
|         } | ||||
|         return response() | ||||
|             ->json(['message' => ctrans('texts.invalid_credentials')], 401) | ||||
|             ->header('X-App-Version', config('ninja.app_version')) | ||||
|             ->header('X-Api-Version', config('ninja.minimum_client_version')); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private function loginOrCreateFromSocialite($user, $provider) | ||||
|     { | ||||
|         $query = [ | ||||
|             'oauth_user_id' => $user->id, | ||||
|             'oauth_provider_id' => $provider, | ||||
|         ]; | ||||
|         if ($existing_user = MultiDB::hasUser($query)) { | ||||
| 
 | ||||
|             if (!$existing_user->account) | ||||
|                 return response()->json(['message' => 'User exists, but not attached to any companies! Orphaned user!'], 400); | ||||
| 
 | ||||
|             Auth::login($existing_user, true); | ||||
| 
 | ||||
|             $cu = $this->hydrateCompanyUser(); | ||||
| 
 | ||||
|             if ($cu->count() == 0) | ||||
|                 return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400); | ||||
| 
 | ||||
|             if (Ninja::isHosted() && !$cu->first()->is_owner && !$existing_user->account->isEnterpriseClient()) | ||||
|                 return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403); | ||||
| 
 | ||||
|             return $this->timeConstrainedResponse($cu); | ||||
| 
 | ||||
|         } | ||||
|         //If this is a result user/email combo - lets add their OAuth details details
 | ||||
|         if ($existing_login_user = MultiDB::hasUser(['email' => $user->email])) { | ||||
|             if (!$existing_login_user->account) | ||||
|                 return response()->json(['message' => 'User exists, but not attached to any companies! Orphaned user!'], 400); | ||||
| 
 | ||||
|             Auth::login($existing_login_user, true); | ||||
| 
 | ||||
|             auth()->user()->update([ | ||||
|                 'oauth_user_id' => $user->id, | ||||
|                 'oauth_provider_id' => $provider, | ||||
|             ]); | ||||
| 
 | ||||
|             $cu = $this->hydrateCompanyUser(); | ||||
| 
 | ||||
|             if ($cu->count() == 0) | ||||
|                 return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400); | ||||
| 
 | ||||
|             if (Ninja::isHosted() && !$cu->first()->is_owner && !$existing_login_user->account->isEnterpriseClient()) | ||||
|                 return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403); | ||||
| 
 | ||||
|             return $this->timeConstrainedResponse($cu); | ||||
|         } | ||||
|         $name = OAuth::splitName($user->name); | ||||
| 
 | ||||
|         $new_account = [ | ||||
|             'first_name' => $name[0], | ||||
|             'last_name' => $name[1], | ||||
|             'password' => '', | ||||
|             'email' => $user->email, | ||||
|             'oauth_user_id' => $user->id, | ||||
|             'oauth_provider_id' => $provider, | ||||
|         ]; | ||||
| 
 | ||||
|         MultiDB::setDefaultDatabase(); | ||||
| 
 | ||||
|         $account = CreateAccount::dispatchNow($new_account, request()->getClientIp()); | ||||
| 
 | ||||
|         Auth::login($account->default_company->owner(), true); | ||||
|         auth()->user()->email_verified_at = now(); | ||||
|         auth()->user()->save(); | ||||
| 
 | ||||
|         $cu = $this->hydrateCompanyUser(); | ||||
| 
 | ||||
|         if ($cu->count() == 0) | ||||
|             return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400); | ||||
| 
 | ||||
|         if (Ninja::isHosted() && !$cu->first()->is_owner && !auth()->user()->account->isEnterpriseClient()) | ||||
|             return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403); | ||||
| 
 | ||||
|         return $this->timeConstrainedResponse($cu); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private function hydrateCompanyUser(): Builder | ||||
|     { | ||||
| 
 | ||||
|         $cu = CompanyUser::query()->where('user_id', auth()->user()->id); | ||||
| 
 | ||||
|         if(CompanyUser::query()->where('user_id', auth()->user()->id)->where('company_id', auth()->user()->account->default_company_id)->exists()) | ||||
|         if (CompanyUser::query()->where('user_id', auth()->user()->id)->where('company_id', auth()->user()->account->default_company_id)->exists()) | ||||
|             $set_company = auth()->user()->account->default_company; | ||||
|         else{ | ||||
|         else { | ||||
|             $set_company = $cu->first()->company; | ||||
|         } | ||||
| 
 | ||||
| @ -392,19 +501,18 @@ class LoginController extends BaseController | ||||
|         if($cu->count() == 0) | ||||
|             return $cu; | ||||
| 
 | ||||
|         if(auth()->user()->company_users()->count() != auth()->user()->tokens()->distinct('company_id')->count()) | ||||
|         { | ||||
|            | ||||
|           auth()->user()->companies->each(function($company){ | ||||
|            | ||||
|             if(!CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $company->id)->exists()){ | ||||
|              | ||||
|               CreateCompanyToken::dispatchNow($company, auth()->user(), "Google_O_Auth"); | ||||
|                | ||||
|         if (auth()->user()->company_users()->count() != auth()->user()->tokens()->distinct('company_id')->count()) { | ||||
| 
 | ||||
|             auth()->user()->companies->each(function ($company) { | ||||
| 
 | ||||
|                 if (!CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $company->id)->exists()) { | ||||
| 
 | ||||
|                     CreateCompanyToken::dispatchNow($company, auth()->user(), "Google_O_Auth"); | ||||
| 
 | ||||
|             } | ||||
|            | ||||
| 
 | ||||
|           }); | ||||
|            | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         $truth->setCompanyToken(CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $set_company->id)->first()); | ||||
| @ -444,7 +552,7 @@ class LoginController extends BaseController | ||||
|                     return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403); | ||||
| 
 | ||||
|                 return $this->timeConstrainedResponse($cu); | ||||
|                  | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|             //If this is a result user/email combo - lets add their OAuth details details
 | ||||
| @ -474,14 +582,14 @@ class LoginController extends BaseController | ||||
|         } | ||||
| 
 | ||||
|         if ($user) { | ||||
|              | ||||
| 
 | ||||
|             //check the user doesn't already exist in some form
 | ||||
| 
 | ||||
|             if($existing_login_user = MultiDB::hasUser(['email' => $google->harvestEmail($user)])) | ||||
|             { | ||||
|                 if(!$existing_login_user->account) | ||||
|                     return response()->json(['message' => 'User exists, but not attached to any companies! Orphaned user!'], 400); | ||||
|                  | ||||
| 
 | ||||
|                 Auth::login($existing_login_user, true); | ||||
| 
 | ||||
|                 auth()->user()->update([ | ||||
| @ -490,11 +598,11 @@ class LoginController extends BaseController | ||||
|                     ]); | ||||
| 
 | ||||
|                 $cu = $this->hydrateCompanyUser(); | ||||
|                  | ||||
| 
 | ||||
|                 // $cu = CompanyUser::query()
 | ||||
|                 //                   ->where('user_id', auth()->user()->id);
 | ||||
| 
 | ||||
|                 if($cu->count() == 0) | ||||
|                 if ($cu->count() == 0) | ||||
|                     return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400); | ||||
| 
 | ||||
|                 if(Ninja::isHosted() && !$cu->first()->is_owner && !$existing_login_user->account->isEnterpriseClient()) | ||||
| @ -557,7 +665,7 @@ class LoginController extends BaseController | ||||
|         if (request()->has('code')) { | ||||
|             return $this->handleProviderCallback($provider); | ||||
|         } else { | ||||
|          | ||||
| 
 | ||||
|             if(!in_array($provider, ['google'])) | ||||
|                 return abort(400, 'Invalid provider'); | ||||
| 
 | ||||
| @ -594,7 +702,7 @@ class LoginController extends BaseController | ||||
|                 'oauth_user_id' => $socialite_user->getId(), | ||||
|                 'oauth_provider_id' => $provider, | ||||
|                 'oauth_user_token' => $oauth_user_token, | ||||
|                 'oauth_user_refresh_token' => $socialite_user->refreshToken  | ||||
|                 'oauth_user_refresh_token' => $socialite_user->refreshToken | ||||
|             ]; | ||||
| 
 | ||||
|             $user->update($update_user); | ||||
|  | ||||
							
								
								
									
										58
									
								
								app/Http/Controllers/Auth/VendorContactLoginController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								app/Http/Controllers/Auth/VendorContactLoginController.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,58 @@ | ||||
| <?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\Http\Controllers\Auth; | ||||
| 
 | ||||
| use App\Events\Contact\ContactLoggedIn; | ||||
| use App\Http\Controllers\Controller; | ||||
| use App\Http\ViewComposers\PortalComposer; | ||||
| use App\Libraries\MultiDB; | ||||
| use App\Models\Account; | ||||
| use App\Models\ClientContact; | ||||
| use App\Models\Company; | ||||
| use App\Utils\Ninja; | ||||
| use Auth; | ||||
| use Illuminate\Foundation\Auth\AuthenticatesUsers; | ||||
| use Illuminate\Http\JsonResponse; | ||||
| use Illuminate\Http\Request; | ||||
| use Illuminate\Support\Facades\Hash; | ||||
| use Route; | ||||
| 
 | ||||
| class VendorContactLoginController extends Controller | ||||
| { | ||||
|     use AuthenticatesUsers; | ||||
| 
 | ||||
|     protected $redirectTo = '/vendor/purchase_orders'; | ||||
| 
 | ||||
|     public function __construct() | ||||
|     { | ||||
|         $this->middleware('guest:vendor', ['except' => ['logout']]); | ||||
|     } | ||||
| 
 | ||||
|     public function catch() | ||||
|     { | ||||
|         $data = [ | ||||
|          | ||||
|         ]; | ||||
| 
 | ||||
|         return $this->render('purchase_orders.catch'); | ||||
|     } | ||||
| 
 | ||||
|     public function logout() | ||||
|     { | ||||
|         Auth::guard('vendor')->logout(); | ||||
|         request()->session()->invalidate(); | ||||
| 
 | ||||
|         return redirect('/vendors'); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -84,6 +84,7 @@ class BaseController extends Controller | ||||
|           'company.products.documents', | ||||
|           'company.payments.paymentables', | ||||
|           'company.payments.documents', | ||||
|           'company.purchase_orders.documents', | ||||
|           'company.payment_terms.company', | ||||
|           'company.projects.documents', | ||||
|           'company.recurring_expenses', | ||||
| @ -171,7 +172,12 @@ class BaseController extends Controller | ||||
|      */ | ||||
|     public function notFoundClient() | ||||
|     { | ||||
|         abort(404, 'Page not found in client portal.'); | ||||
|         abort(404, 'Page not found in the client portal.'); | ||||
|     } | ||||
| 
 | ||||
|     public function notFoundVendor() | ||||
|     { | ||||
|         abort(404, 'Page not found in the vendor portal.'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -296,6 +302,13 @@ class BaseController extends Controller | ||||
|                 if(!$user->hasPermission('view_project')) | ||||
|                   $query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id); | ||||
| 
 | ||||
|             }, | ||||
|             'company.purchase_orders'=> function ($query) use ($updated_at, $user) { | ||||
|                 $query->where('updated_at', '>=', $updated_at)->with('documents'); | ||||
| 
 | ||||
|                 if(!$user->hasPermission('view_purchase_order')) | ||||
|                   $query->where('purchase_orders.user_id', $user->id)->orWhere('purchase_orders.assigned_user_id', $user->id); | ||||
| 
 | ||||
|             }, | ||||
|             'company.quotes'=> function ($query) use ($updated_at, $user) { | ||||
|                 $query->where('updated_at', '>=', $updated_at)->with('invitations', 'documents'); | ||||
| @ -533,6 +546,13 @@ class BaseController extends Controller | ||||
|                 if(!$user->hasPermission('view_project')) | ||||
|                   $query->where('projects.user_id', $user->id)->orWhere('projects.assigned_user_id', $user->id); | ||||
| 
 | ||||
|             }, | ||||
|             'company.purchase_orders'=> function ($query) use ($created_at, $user) { | ||||
|                 $query->where('created_at', '>=', $created_at)->with('documents'); | ||||
| 
 | ||||
|                 if(!$user->hasPermission('view_purchase_order')) | ||||
|                   $query->where('purchase_orders.user_id', $user->id)->orWhere('purchase_orders.assigned_user_id', $user->id); | ||||
| 
 | ||||
|             }, | ||||
|             'company.quotes'=> function ($query) use ($created_at, $user) { | ||||
|                 $query->where('created_at', '>=', $created_at)->with('invitations', 'documents'); | ||||
| @ -780,7 +800,7 @@ class BaseController extends Controller | ||||
| 
 | ||||
|             $this->buildCache(); | ||||
| 
 | ||||
|             if(config('ninja.react_app_enabled')) | ||||
|             if(Ninja::isSelfHost() && $account->set_react_as_default_ap) | ||||
|                 return response()->view('react.index', $data)->header('X-Frame-Options', 'SAMEORIGIN', false); | ||||
|             else | ||||
|                 return response()->view('index.index', $data)->header('X-Frame-Options', 'SAMEORIGIN', false); | ||||
|  | ||||
| @ -154,9 +154,11 @@ class EntityViewController extends Controller | ||||
|         if (! $invitation->viewed_date) { | ||||
|             $invitation->markViewed(); | ||||
| 
 | ||||
|             event(new InvitationWasViewed($invitation->{$request->entity_type}, $invitation, $invitation->{$request->entity_type}->company, Ninja::eventVars())); | ||||
|             if(!session()->get('is_silent')) | ||||
|                 event(new InvitationWasViewed($invitation->{$request->entity_type}, $invitation, $invitation->{$request->entity_type}->company, Ninja::eventVars())); | ||||
| 
 | ||||
|             $this->fireEntityViewedEvent($invitation, $request->entity_type); | ||||
|             if(!session()->get('is_silent')) | ||||
|                 $this->fireEntityViewedEvent($invitation, $request->entity_type); | ||||
|         } | ||||
|          | ||||
|         return redirect()->route('client.'.$request->entity_type.'.show', [$request->entity_type => $this->encodePrimaryKey($invitation->{$key})]); | ||||
|  | ||||
| @ -129,9 +129,11 @@ class InvitationController extends Controller | ||||
|         if (auth()->guard('contact')->user() && ! request()->has('silent') && ! $invitation->viewed_date) { | ||||
|             $invitation->markViewed(); | ||||
| 
 | ||||
|             event(new InvitationWasViewed($invitation->{$entity}, $invitation, $invitation->{$entity}->company, Ninja::eventVars())); | ||||
|             if(!session()->get('is_silent')) | ||||
|                 event(new InvitationWasViewed($invitation->{$entity}, $invitation, $invitation->{$entity}->company, Ninja::eventVars())); | ||||
| 
 | ||||
|             $this->fireEntityViewedEvent($invitation, $entity); | ||||
|             if(!session()->get('is_silent')) | ||||
|                 $this->fireEntityViewedEvent($invitation, $entity); | ||||
|         } | ||||
|         else{ | ||||
|             $is_silent = 'true'; | ||||
|  | ||||
| @ -61,7 +61,7 @@ class InvoiceController extends Controller | ||||
| 
 | ||||
|         $invitation = $invoice->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first(); | ||||
| 
 | ||||
|             if ($invitation && auth()->guard('contact') && ! request()->has('silent') && ! $invitation->viewed_date) { | ||||
|             if ($invitation && auth()->guard('contact') && !session()->get('is_silent') && ! $invitation->viewed_date) { | ||||
| 
 | ||||
|                 $invitation->markViewed(); | ||||
| 
 | ||||
|  | ||||
| @ -154,6 +154,7 @@ class NinjaPlanController extends Controller | ||||
|         $recurring_invoice->auto_bill_enabled =  $this->setAutoBillFlag($recurring_invoice->auto_bill); | ||||
|         $recurring_invoice->due_date_days = 'terms'; | ||||
|         $recurring_invoice->next_send_date = now()->addDays(14)->format('Y-m-d'); | ||||
|         $recurring_invoice->next_send_date_client = now()->addDays(14)->format('Y-m-d'); | ||||
| 
 | ||||
|         $recurring_invoice->save(); | ||||
|         $r = $recurring_invoice->calc()->getRecurringInvoice(); | ||||
|  | ||||
| @ -24,6 +24,8 @@ use App\Http\Requests\PurchaseOrder\ShowPurchaseOrderRequest; | ||||
| use App\Http\Requests\PurchaseOrder\StorePurchaseOrderRequest; | ||||
| use App\Http\Requests\PurchaseOrder\UpdatePurchaseOrderRequest; | ||||
| use App\Jobs\Invoice\ZipInvoices; | ||||
| use App\Jobs\PurchaseOrder\PurchaseOrderEmail; | ||||
| use App\Jobs\PurchaseOrder\ZipPurchaseOrders; | ||||
| use App\Models\Client; | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Repositories\PurchaseOrderRepository; | ||||
| @ -31,6 +33,7 @@ use App\Transformers\PurchaseOrderTransformer; | ||||
| use App\Utils\Ninja; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use Illuminate\Http\Response; | ||||
| use Illuminate\Support\Facades\Storage; | ||||
| 
 | ||||
| class PurchaseOrderController extends BaseController | ||||
| { | ||||
| @ -183,6 +186,7 @@ class PurchaseOrderController extends BaseController | ||||
| 
 | ||||
|         $purchase_order = $purchase_order->service() | ||||
|             ->fillDefaults() | ||||
|             ->triggeredActions($request) | ||||
|             ->save(); | ||||
| 
 | ||||
|         event(new PurchaseOrderWasCreated($purchase_order, $purchase_order->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); | ||||
| @ -485,7 +489,7 @@ class PurchaseOrderController extends BaseController | ||||
|          * Download Purchase Order/s | ||||
|          */ | ||||
| 
 | ||||
|         if ($action == 'bulk_download' && $purchase_orders->count() > 1) { | ||||
|         if ($action == 'bulk_download' && $purchase_orders->count() >= 1) { | ||||
|             $purchase_orders->each(function ($purchase_order) { | ||||
|                 if (auth()->user()->cannot('view', $purchase_order)) { | ||||
|                     nlog("access denied"); | ||||
| @ -493,7 +497,7 @@ class PurchaseOrderController extends BaseController | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             ZipInvoices::dispatch($purchase_orders, $purchase_orders->first()->company, auth()->user()); | ||||
|             ZipPurchaseOrders::dispatch($purchase_orders, $purchase_orders->first()->company, auth()->user()); | ||||
| 
 | ||||
|             return response()->json(['message' => ctrans('texts.sent_message')], 200); | ||||
|         } | ||||
| @ -579,7 +583,7 @@ class PurchaseOrderController extends BaseController | ||||
|      */ | ||||
|     public function action(ActionPurchaseOrderRequest $request, PurchaseOrder $purchase_order, $action) | ||||
|     { | ||||
|         return $this->performAction($invoice, $action); | ||||
|         return $this->performAction($purchase_order, $action); | ||||
|     } | ||||
| 
 | ||||
|     private function performAction(PurchaseOrder $purchase_order, $action, $bulk = false) | ||||
| @ -627,8 +631,13 @@ class PurchaseOrderController extends BaseController | ||||
|              | ||||
|             case 'email': | ||||
|                 //check query parameter for email_type and set the template else use calculateTemplate
 | ||||
|                 PurchaseOrderEmail::dispatch($purchase_order, $purchase_order->company); | ||||
| 
 | ||||
| 
 | ||||
|                 if (! $bulk) { | ||||
|                     return response()->json(['message' => 'email sent'], 200); | ||||
|                 } | ||||
| 
 | ||||
|             default: | ||||
|                 return response()->json(['message' => ctrans('texts.action_unavailable', ['action' => $action])], 400); | ||||
|                 break; | ||||
|  | ||||
| @ -135,6 +135,9 @@ class SelfUpdateController extends BaseController | ||||
| 
 | ||||
|         nlog("Extracting zip"); | ||||
| 
 | ||||
|         //clean up old snappdf installations
 | ||||
|         $this->cleanOldSnapChromeBinaries(); | ||||
|          | ||||
|         // try{
 | ||||
|         //     $s = new Snappdf;
 | ||||
|         //     $s->getChromiumPath();
 | ||||
| @ -190,6 +193,46 @@ class SelfUpdateController extends BaseController | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private function cleanOldSnapChromeBinaries() | ||||
|     { | ||||
|         $current_revision =  base_path('vendor/beganovich/snappdf/versions/revision.txt'); | ||||
|         $current_revision_text = file_get_contents($current_revision); | ||||
| 
 | ||||
|         $iterator = new \DirectoryIterator(base_path('vendor/beganovich/snappdf/versions')); | ||||
| 
 | ||||
|         foreach ($iterator as $file)  | ||||
|         { | ||||
| 
 | ||||
|             if($file->isDir() && !$file->isDot() && ($current_revision_text != $file->getFileName())) | ||||
|             { | ||||
| 
 | ||||
|                 $directoryIterator = new \RecursiveDirectoryIterator(base_path('vendor/beganovich/snappdf/versions/'.$file->getFileName()), \RecursiveDirectoryIterator::SKIP_DOTS); | ||||
| 
 | ||||
|                 foreach (new \RecursiveIteratorIterator($directoryIterator) as $filex)  | ||||
|                 { | ||||
|                   unlink($filex->getPathName()); | ||||
|                 } | ||||
| 
 | ||||
|                 $this->deleteDirectory(base_path('vendor/beganovich/snappdf/versions/'.$file->getFileName())); | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private function deleteDirectory($dir) { | ||||
|         if (!file_exists($dir)) return true; | ||||
|      | ||||
|         if (!is_dir($dir) || is_link($dir)) return unlink($dir); | ||||
|             foreach (scandir($dir) as $item) { | ||||
|                 if ($item == '.' || $item == '..') continue; | ||||
|                 if (!$this->deleteDirectory($dir . "/" . $item)) { | ||||
|                     if (!$this->deleteDirectory($dir . "/" . $item)) return false; | ||||
|                 }; | ||||
|             } | ||||
|             return rmdir($dir); | ||||
|     } | ||||
| 
 | ||||
|     private function postHookUpdate() | ||||
|     { | ||||
|         if(config('ninja.app_version') == '5.3.82') | ||||
|  | ||||
| @ -35,6 +35,7 @@ class SubdomainController extends BaseController | ||||
|         'html', | ||||
|         'lb', | ||||
|         'shopify', | ||||
|         'beta', | ||||
|     ]; | ||||
| 
 | ||||
|     public function __construct() | ||||
|  | ||||
							
								
								
									
										144
									
								
								app/Http/Controllers/VendorPortal/InvitationController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								app/Http/Controllers/VendorPortal/InvitationController.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,144 @@ | ||||
| <?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\Http\Controllers\VendorPortal; | ||||
| 
 | ||||
| use App\Events\Credit\CreditWasViewed; | ||||
| use App\Events\Invoice\InvoiceWasViewed; | ||||
| use App\Events\Misc\InvitationWasViewed; | ||||
| use App\Events\Quote\QuoteWasViewed; | ||||
| use App\Http\Controllers\Controller; | ||||
| use App\Jobs\Entity\CreateRawPdf; | ||||
| use App\Models\Client; | ||||
| use App\Models\ClientContact; | ||||
| use App\Models\CreditInvitation; | ||||
| use App\Models\InvoiceInvitation; | ||||
| use App\Models\Payment; | ||||
| use App\Models\PurchaseOrderInvitation; | ||||
| use App\Models\QuoteInvitation; | ||||
| use App\Services\ClientPortal\InstantPayment; | ||||
| use App\Utils\CurlUtils; | ||||
| use App\Utils\Ninja; | ||||
| use App\Utils\Traits\MakesDates; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use Illuminate\Http\Request; | ||||
| use Illuminate\Support\Facades\Auth; | ||||
| use Illuminate\Support\Str; | ||||
| 
 | ||||
| /** | ||||
|  * Class InvitationController. | ||||
|  */ | ||||
| class InvitationController extends Controller | ||||
| { | ||||
|     use MakesHash; | ||||
|     use MakesDates; | ||||
| 
 | ||||
|     public function purchaseOrder(string $invitation_key) | ||||
|     { | ||||
| 
 | ||||
|         Auth::logout(); | ||||
| 
 | ||||
|         $invitation = PurchaseOrderInvitation::where('key', $invitation_key) | ||||
|                                     ->whereHas('purchase_order', function ($query) { | ||||
|                                          $query->where('is_deleted',0); | ||||
|                                     }) | ||||
|                                     ->with('contact.vendor') | ||||
|                                     ->first(); | ||||
| 
 | ||||
|         if(!$invitation) | ||||
|             return abort(404,'The resource is no longer available.'); | ||||
| 
 | ||||
|         if($invitation->contact->trashed()) | ||||
|             $invitation->contact->restore(); | ||||
| 
 | ||||
|         $vendor_contact = $invitation->contact; | ||||
|         $entity = 'purchase_order'; | ||||
| 
 | ||||
|         if(empty($vendor_contact->email)) | ||||
|             $vendor_contact->email = Str::random(15) . "@example.com"; $vendor_contact->save(); | ||||
| 
 | ||||
|         if (request()->has('vendor_hash') && request()->input('vendor_hash') == $invitation->contact->vendor->vendor_hash) { | ||||
|             request()->session()->invalidate(); | ||||
|             auth()->guard('vendor')->loginUsingId($vendor_contact->id, true); | ||||
| 
 | ||||
|         } else { | ||||
|             nlog("else - default - login contact"); | ||||
|             request()->session()->invalidate(); | ||||
|             auth()->guard('vendor')->loginUsingId($vendor_contact->id, true); | ||||
|         } | ||||
| 
 | ||||
|         session()->put('is_silent', request()->has('silent')); | ||||
| 
 | ||||
|         if (auth()->guard('vendor')->user() && ! session()->get('is_silent') && ! $invitation->viewed_date) { | ||||
| 
 | ||||
|             $invitation->markViewed(); | ||||
|             event(new InvitationWasViewed($invitation->purchase_order, $invitation, $invitation->company, Ninja::eventVars())); | ||||
|              | ||||
|         } | ||||
|         else{ | ||||
| 
 | ||||
|             return redirect()->route('vendor.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->purchase_order_id), 'silent' => session()->get('is_silent')]); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         return redirect()->route('vendor.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->purchase_order_id)]); | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     // public function routerForDownload(string $entity, string $invitation_key)
 | ||||
|     // {
 | ||||
| 
 | ||||
|     //     set_time_limit(45);
 | ||||
| 
 | ||||
|     //     if(Ninja::isHosted())
 | ||||
|     //         return $this->returnRawPdf($entity, $invitation_key);
 | ||||
| 
 | ||||
|     //     return redirect('client/'.$entity.'/'.$invitation_key.'/download_pdf');
 | ||||
|     // }
 | ||||
| 
 | ||||
|     // private function returnRawPdf(string $entity, string $invitation_key)
 | ||||
|     // {
 | ||||
| 
 | ||||
|     //     if(!in_array($entity, ['invoice', 'credit', 'quote', 'recurring_invoice']))
 | ||||
|     //         return response()->json(['message' => 'Invalid resource request']);
 | ||||
| 
 | ||||
|     //     $key = $entity.'_id';
 | ||||
| 
 | ||||
|     //     $entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
 | ||||
| 
 | ||||
|     //     $invitation = $entity_obj::where('key', $invitation_key)
 | ||||
|     //                                 ->with('contact.client')
 | ||||
|     //                                 ->firstOrFail();
 | ||||
| 
 | ||||
|     //     if(!$invitation)
 | ||||
|     //         return response()->json(["message" => "no record found"], 400);
 | ||||
| 
 | ||||
|     //     $file_name = $invitation->purchase_order->numberFormatter().'.pdf';
 | ||||
| 
 | ||||
|     //     $file = CreateRawPdf::dispatchNow($invitation, $invitation->company->db);
 | ||||
| 
 | ||||
|     //     $headers = ['Content-Type' => 'application/pdf'];
 | ||||
| 
 | ||||
|     //     if(request()->input('inline') == 'true')
 | ||||
|     //         $headers = array_merge($headers, ['Content-Disposition' => 'inline']);
 | ||||
| 
 | ||||
|     //     return response()->streamDownload(function () use($file) {
 | ||||
|     //             echo $file;
 | ||||
|     //     },  $file_name, $headers);
 | ||||
| 
 | ||||
|     // }
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										229
									
								
								app/Http/Controllers/VendorPortal/PurchaseOrderController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								app/Http/Controllers/VendorPortal/PurchaseOrderController.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,229 @@ | ||||
| <?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\Http\Controllers\VendorPortal; | ||||
| 
 | ||||
| use App\Events\Misc\InvitationWasViewed; | ||||
| use App\Events\PurchaseOrder\PurchaseOrderWasAccepted; | ||||
| use App\Events\PurchaseOrder\PurchaseOrderWasViewed; | ||||
| use App\Http\Controllers\Controller; | ||||
| use App\Http\Requests\VendorPortal\PurchaseOrders\ProcessPurchaseOrdersInBulkRequest; | ||||
| use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrderRequest; | ||||
| use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrdersRequest; | ||||
| use App\Jobs\Invoice\InjectSignature; | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Utils\Ninja; | ||||
| use App\Utils\Traits\MakesDates; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use Illuminate\Contracts\View\Factory; | ||||
| use Illuminate\Http\Request; | ||||
| use Illuminate\Support\Facades\Storage; | ||||
| use Illuminate\View\View; | ||||
| 
 | ||||
| class PurchaseOrderController extends Controller | ||||
| { | ||||
|     use MakesHash, MakesDates; | ||||
| 
 | ||||
|     public const MODULE_RECURRING_INVOICES = 1; | ||||
|     public const MODULE_CREDITS = 2; | ||||
|     public const MODULE_QUOTES = 4; | ||||
|     public const MODULE_TASKS = 8; | ||||
|     public const MODULE_EXPENSES = 16; | ||||
|     public const MODULE_PROJECTS = 32; | ||||
|     public const MODULE_VENDORS = 64; | ||||
|     public const MODULE_TICKETS = 128; | ||||
|     public const MODULE_PROPOSALS = 256; | ||||
|     public const MODULE_RECURRING_EXPENSES = 512; | ||||
|     public const MODULE_RECURRING_TASKS = 1024; | ||||
|     public const MODULE_RECURRING_QUOTES = 2048; | ||||
|     public const MODULE_INVOICES = 4096; | ||||
|     public const MODULE_PROFORMAL_INVOICES = 8192; | ||||
|     public const MODULE_PURCHASE_ORDERS = 16384; | ||||
| 
 | ||||
|     /** | ||||
|      * Display list of invoices. | ||||
|      * | ||||
|      * @return Factory|View | ||||
|      */ | ||||
|     public function index(ShowPurchaseOrdersRequest $request) | ||||
|     { | ||||
| 
 | ||||
|         return $this->render('purchase_orders.index', ['company' => auth()->user()->company, 'settings' => auth()->user()->company->settings, 'sidebar' => $this->sidebarMenu()]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Show specific invoice. | ||||
|      * | ||||
|      * @param ShowInvoiceRequest $request | ||||
|      * @param Invoice $invoice | ||||
|      * | ||||
|      * @return Factory|View | ||||
|      */ | ||||
|     public function show(ShowPurchaseOrderRequest $request, PurchaseOrder $purchase_order) | ||||
|     { | ||||
|         set_time_limit(0); | ||||
| 
 | ||||
|         $invitation = $purchase_order->invitations()->where('vendor_contact_id', auth()->guard('vendor')->user()->id)->first(); | ||||
| 
 | ||||
|             if ($invitation && auth()->guard('vendor') && !session()->get('is_silent') && ! $invitation->viewed_date) { | ||||
| 
 | ||||
|                 $invitation->markViewed(); | ||||
| 
 | ||||
|                 event(new InvitationWasViewed($purchase_order, $invitation, $purchase_order->company, Ninja::eventVars())); | ||||
|                 event(new PurchaseOrderWasViewed($invitation, $invitation->company, Ninja::eventVars())); | ||||
|              | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|         $data = [ | ||||
|             'purchase_order' => $purchase_order, | ||||
|             'key' => $invitation ? $invitation->key : false, | ||||
|             'settings' => $purchase_order->company->settings, | ||||
|             'sidebar' => $this->sidebarMenu(), | ||||
|             'company' => $purchase_order->company | ||||
|         ]; | ||||
| 
 | ||||
|         if ($request->query('mode') === 'fullscreen') { | ||||
|             return render('purchase_orders.show-fullscreen', $data); | ||||
|         } | ||||
| 
 | ||||
|         return $this->render('purchase_orders.show', $data); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     private function sidebarMenu() :array | ||||
|     { | ||||
|         $enabled_modules = auth()->guard('vendor')->user()->company->enabled_modules; | ||||
|         $data = []; | ||||
| 
 | ||||
|         // TODO: Enable dashboard once it's completed.
 | ||||
|         // $this->settings->enable_client_portal_dashboard
 | ||||
|         // $data[] = [ 'title' => ctrans('texts.dashboard'), 'url' => 'client.dashboard', 'icon' => 'activity'];
 | ||||
| 
 | ||||
|         if (self::MODULE_PURCHASE_ORDERS & $enabled_modules) { | ||||
|             $data[] = ['title' => ctrans('texts.purchase_orders'), 'url' => 'vendor.purchase_orders.index', 'icon' => 'file-text']; | ||||
|         } | ||||
| 
 | ||||
|         // $data[] = ['title' => ctrans('texts.documents'), 'url' => 'client.documents.index', 'icon' => 'download'];
 | ||||
| 
 | ||||
|         return $data; | ||||
|     } | ||||
| 
 | ||||
|     public function bulk(ProcessPurchaseOrdersInBulkRequest $request) | ||||
|     { | ||||
| 
 | ||||
|         $transformed_ids = $this->transformKeys($request->purchase_orders); | ||||
| 
 | ||||
|         if ($request->input('action') == 'download') { | ||||
|             return $this->downloadInvoices((array) $transformed_ids); | ||||
|         } | ||||
|         elseif ($request->input('action') == 'accept'){ | ||||
|             return $this->acceptPurchaseOrder($request->all()); | ||||
|         } | ||||
| 
 | ||||
|         return redirect() | ||||
|             ->back() | ||||
|             ->with('message', ctrans('texts.no_action_provided')); | ||||
|     } | ||||
| 
 | ||||
|     public function acceptPurchaseOrder($data) | ||||
|     { | ||||
|         $purchase_orders = PurchaseOrder::query() | ||||
|                                         ->whereIn('id', $this->transformKeys($data['purchase_orders'])) | ||||
|                                         ->where('company_id', auth()->guard('vendor')->user()->vendor->company_id) | ||||
|                                         ->whereIn('status_id', [PurchaseOrder::STATUS_DRAFT, PurchaseOrder::STATUS_SENT]) | ||||
|                                         ->cursor()->each(function ($purchase_order){ | ||||
| 
 | ||||
|                                         $purchase_order->service() | ||||
|                                                        ->markSent() | ||||
|                                                        ->applyNumber() | ||||
|                                                        ->setStatus(PurchaseOrder::STATUS_ACCEPTED) | ||||
|                                                        ->save(); | ||||
| 
 | ||||
|                                         if (request()->has('signature') && !is_null(request()->signature) && !empty(request()->signature)) { | ||||
|                                             InjectSignature::dispatch($purchase_order, request()->signature); | ||||
|                                         } | ||||
|                                          | ||||
|                                         event(new PurchaseOrderWasAccepted($purchase_order, auth()->guard('vendor')->user(), $purchase_order->company, Ninja::eventVars())); | ||||
| 
 | ||||
|         }); | ||||
| 
 | ||||
|         if(count($data['purchase_orders']) == 1){  | ||||
| 
 | ||||
|             $purchase_order = PurchaseOrder::whereIn('id', $this->transformKeys($data['purchase_orders']))->first(); | ||||
|              | ||||
|             return redirect()->route('vendor.purchase_order.show', ['purchase_order' => $purchase_order->hashed_id]); | ||||
|          | ||||
|         } | ||||
|         else | ||||
|             return redirect()->route('vendor.purchase_orders.index'); | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function downloadInvoices($ids) | ||||
|     { | ||||
| 
 | ||||
|         $purchase_orders = PurchaseOrder::whereIn('id', $ids) | ||||
|                             ->where('vendor_id', auth()->guard('vendor')->user()->vendor_id) | ||||
|                             ->withTrashed() | ||||
|                             ->get(); | ||||
| 
 | ||||
|         if(count($purchase_orders) == 0) | ||||
|             return back()->with(['message' => ctrans('texts.no_items_selected')]); | ||||
| 
 | ||||
| 
 | ||||
|         if(count($purchase_orders) == 1){ | ||||
| 
 | ||||
|            $purchase_order = $purchase_orders->first(); | ||||
| 
 | ||||
|            $file = $purchase_order->service()->getPurchaseOrderPdf(auth()->guard('vendor')->user()); | ||||
| 
 | ||||
|             return response()->streamDownload(function () use($file) { | ||||
|                     echo Storage::get($file); | ||||
|             },  basename($file), ['Content-Type' => 'application/pdf']); | ||||
|         } | ||||
| 
 | ||||
|         return $this->buildZip($purchase_orders); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private function buildZip($purchase_orders) | ||||
|     { | ||||
|         // create new archive
 | ||||
|         $zipFile = new \PhpZip\ZipFile(); | ||||
|         try{ | ||||
|              | ||||
|             foreach ($purchase_orders as $purchase_order) { | ||||
| 
 | ||||
|                 #add it to the zip
 | ||||
|                 $zipFile->addFromString(basename($purchase_order->pdf_file_path()), file_get_contents($purchase_order->pdf_file_path(null, 'url', true))); | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|             $filename = date('Y-m-d').'_'.str_replace(' ', '_', trans('texts.purchase_orders')).'.zip'; | ||||
|             $filepath = sys_get_temp_dir() . '/' . $filename; | ||||
| 
 | ||||
|            $zipFile->saveAsFile($filepath) // save the archive to a file
 | ||||
|                    ->close(); // close archive
 | ||||
|                      | ||||
|            return response()->download($filepath, $filename)->deleteFileAfterSend(true); | ||||
| 
 | ||||
|         } | ||||
|         catch(\PhpZip\Exception\ZipException $e){ | ||||
|             // handle exception
 | ||||
|         } | ||||
|         finally{ | ||||
|             $zipFile->close(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,81 @@ | ||||
| <?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\Http\Controllers\VendorPortal; | ||||
| 
 | ||||
| use App\Http\Controllers\Controller; | ||||
| use App\Models\VendorContact; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use App\Utils\TranslationHelper; | ||||
| use Illuminate\Http\Request; | ||||
| 
 | ||||
| class VendorContactController extends Controller | ||||
| { | ||||
|     use MakesHash; | ||||
| 
 | ||||
|     public const MODULE_RECURRING_INVOICES = 1; | ||||
|     public const MODULE_CREDITS = 2; | ||||
|     public const MODULE_QUOTES = 4; | ||||
|     public const MODULE_TASKS = 8; | ||||
|     public const MODULE_EXPENSES = 16; | ||||
|     public const MODULE_PROJECTS = 32; | ||||
|     public const MODULE_VENDORS = 64; | ||||
|     public const MODULE_TICKETS = 128; | ||||
|     public const MODULE_PROPOSALS = 256; | ||||
|     public const MODULE_RECURRING_EXPENSES = 512; | ||||
|     public const MODULE_RECURRING_TASKS = 1024; | ||||
|     public const MODULE_RECURRING_QUOTES = 2048; | ||||
|     public const MODULE_INVOICES = 4096; | ||||
|     public const MODULE_PROFORMAL_INVOICES = 8192; | ||||
|     public const MODULE_PURCHASE_ORDERS = 16384; | ||||
| 
 | ||||
|     public function edit(VendorContact $vendor_contact) | ||||
|     { | ||||
| 
 | ||||
|         return $this->render('vendor_profile.edit', [ | ||||
|             'contact' => $vendor_contact, | ||||
|             'vendor' => $vendor_contact->vendor, | ||||
|             'settings' => $vendor_contact->vendor->company->settings, | ||||
|             'company' => $vendor_contact->vendor->company, | ||||
|             'sidebar' => $this->sidebarMenu(), | ||||
|             'countries' => TranslationHelper::getCountries() | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public function update(VendorContact $vendor_contact) | ||||
|     { | ||||
|         $vendor_contact->fill(request()->all()); | ||||
|         $vendor_contact->vendor->fill(request()->all()); | ||||
|         $vendor_contact->push(); | ||||
| 
 | ||||
|          return back()->withSuccess(ctrans('texts.profile_updated_successfully')); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private function sidebarMenu() :array | ||||
|     { | ||||
|         $enabled_modules = auth()->guard('vendor')->user()->company->enabled_modules; | ||||
|         $data = []; | ||||
| 
 | ||||
|         // TODO: Enable dashboard once it's completed.
 | ||||
|         // $this->settings->enable_client_portal_dashboard
 | ||||
|         // $data[] = [ 'title' => ctrans('texts.dashboard'), 'url' => 'client.dashboard', 'icon' => 'activity'];
 | ||||
| 
 | ||||
|         if (self::MODULE_PURCHASE_ORDERS & $enabled_modules) { | ||||
|             $data[] = ['title' => ctrans('texts.purchase_orders'), 'url' => 'vendor.purchase_orders.index', 'icon' => 'file-text']; | ||||
|         } | ||||
| 
 | ||||
|         // $data[] = ['title' => ctrans('texts.documents'), 'url' => 'client.documents.index', 'icon' => 'download'];
 | ||||
| 
 | ||||
|         return $data; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -29,7 +29,7 @@ class WePayController extends BaseController | ||||
|      */ | ||||
|     public function signup(string $token) | ||||
|     { | ||||
|         return render('gateways.wepay.signup.finished'); | ||||
|         // return render('gateways.wepay.signup.finished');
 | ||||
| 
 | ||||
|         $hash = Cache::get($token); | ||||
| 
 | ||||
|  | ||||
| @ -42,6 +42,7 @@ use App\Http\Middleware\TrimStrings; | ||||
| use App\Http\Middleware\TrustProxies; | ||||
| use App\Http\Middleware\UrlSetDb; | ||||
| use App\Http\Middleware\UserVerified; | ||||
| use App\Http\Middleware\VendorLocale; | ||||
| use App\Http\Middleware\VerifyCsrfToken; | ||||
| use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; | ||||
| use Illuminate\Auth\Middleware\Authorize; | ||||
| @ -158,6 +159,7 @@ class Kernel extends HttpKernel | ||||
|         'api_db' => SetDb::class, | ||||
|         'company_key_db' => SetDbByCompanyKey::class, | ||||
|         'locale' => Locale::class, | ||||
|         'vendor_locale' => VendorLocale::class, | ||||
|         'contact_register' => ContactRegister::class, | ||||
|         'shop_token_auth' => ShopTokenAuth::class, | ||||
|         'phantom_secret' => PhantomSecret::class, | ||||
|  | ||||
							
								
								
									
										76
									
								
								app/Http/Livewire/PurchaseOrdersTable.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								app/Http/Livewire/PurchaseOrdersTable.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | ||||
| <?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\Http\Livewire; | ||||
| 
 | ||||
| use App\Libraries\MultiDB; | ||||
| use App\Models\Invoice; | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Utils\Traits\WithSorting; | ||||
| use Carbon\Carbon; | ||||
| use Livewire\Component; | ||||
| use Livewire\WithPagination; | ||||
| 
 | ||||
| class PurchaseOrdersTable extends Component | ||||
| { | ||||
|     use WithPagination, WithSorting; | ||||
| 
 | ||||
|     public $per_page = 10; | ||||
| 
 | ||||
|     public $status = []; | ||||
| 
 | ||||
|     public $company; | ||||
|      | ||||
|     public function mount() | ||||
|     { | ||||
|         MultiDB::setDb($this->company->db); | ||||
| 
 | ||||
|         $this->sort_asc = false; | ||||
| 
 | ||||
|         $this->sort_field = 'date'; | ||||
|     } | ||||
| 
 | ||||
|     public function render() | ||||
|     { | ||||
|         $local_status = []; | ||||
| 
 | ||||
|         $query = PurchaseOrder::query() | ||||
|             ->with('vendor.contacts') | ||||
|             ->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc') | ||||
|             ->whereIn('status_id', [PurchaseOrder::STATUS_SENT, PurchaseOrder::STATUS_ACCEPTED]) | ||||
|             ->where('company_id', $this->company->id) | ||||
|             ->where('is_deleted', false); | ||||
| 
 | ||||
|         if (in_array('sent', $this->status)) { | ||||
|             $local_status[] = PurchaseOrder::STATUS_SENT; | ||||
|         } | ||||
| 
 | ||||
|         if (in_array('accepted', $this->status)) { | ||||
|             $local_status[] = PurchaseOrder::STATUS_ACCEPTED; | ||||
|         } | ||||
| 
 | ||||
|         if (count($local_status) > 0) { | ||||
|             $query = $query->whereIn('status_id', array_unique($local_status)); | ||||
|         } | ||||
| 
 | ||||
|         $query = $query | ||||
|             ->where('vendor_id', auth()->guard('vendor')->user()->vendor_id) | ||||
|             // ->where('status_id', '<>', Invoice::STATUS_DRAFT)
 | ||||
|             // ->where('status_id', '<>', Invoice::STATUS_CANCELLED)
 | ||||
|             ->withTrashed() | ||||
|             ->paginate($this->per_page); | ||||
| 
 | ||||
|         return render('components.livewire.purchase-orders-table', [ | ||||
|             'purchase_orders' => $query | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
| @ -60,6 +60,8 @@ class CheckClientExistence | ||||
| 
 | ||||
|         session()->put('multiple_contacts', $multiple_contacts); | ||||
| 
 | ||||
|         session()->put('is_silent', request()->has('silent')); | ||||
| 
 | ||||
|         return $next($request); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -20,32 +20,38 @@ class RedirectIfAuthenticated | ||||
|     /** | ||||
|      * Handle an incoming request. | ||||
|      * | ||||
|      * @param  Request  $request | ||||
|      * @param Request $request | ||||
|      * @param Closure $next | ||||
|      * @param  string|null  $guard | ||||
|      * @param string|null $guard | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function handle($request, Closure $next, $guard = null) | ||||
|     { | ||||
|         switch ($guard) { | ||||
|         case 'contact': | ||||
|           if (Auth::guard($guard)->check()) { | ||||
|               return redirect()->route('client.dashboard'); | ||||
|           } | ||||
|           break; | ||||
|           case 'user': | ||||
|           Auth::logout(); | ||||
|           // if (Auth::guard($guard)->check()) {
 | ||||
|           //     return redirect()->route('dashboard.index');
 | ||||
|           // }
 | ||||
|           break; | ||||
|         default: | ||||
|         Auth::logout(); | ||||
|           // if (Auth::guard($guard)->check()) {
 | ||||
|           //     return redirect('/');
 | ||||
|           // }
 | ||||
|           break; | ||||
|       } | ||||
|             case 'contact': | ||||
|                 if (Auth::guard($guard)->check()) { | ||||
|                     return redirect()->route('client.dashboard'); | ||||
|                 } | ||||
|                 break; | ||||
|             case 'user': | ||||
|                 Auth::logout(); | ||||
|                 // if (Auth::guard($guard)->check()) {
 | ||||
|                 //     return redirect()->route('dashboard.index');
 | ||||
|                 // }
 | ||||
|                 break; | ||||
|             case 'vendor': | ||||
|                 if (Auth::guard($guard)->check()) { | ||||
|                     //TODO create routes for vendor
 | ||||
|                   //  return redirect()->route('vendor.dashboard');
 | ||||
|                 } | ||||
|                 break; | ||||
|             default: | ||||
|                 Auth::logout(); | ||||
|                 // if (Auth::guard($guard)->check()) {
 | ||||
|                 //     return redirect('/');
 | ||||
|                 // }
 | ||||
|                 break; | ||||
|         } | ||||
| 
 | ||||
|         return $next($request); | ||||
|     } | ||||
|  | ||||
| @ -46,7 +46,7 @@ class SetInviteDb | ||||
|         if($entity == "pay") | ||||
|             $entity = "invoice"; | ||||
| 
 | ||||
|         if(!in_array($entity, ['invoice','quote','credit','recurring_invoice'])) | ||||
|         if(!in_array($entity, ['invoice','quote','credit','recurring_invoice','purchase_order'])) | ||||
|             abort(404,'I could not find this resource.'); | ||||
| 
 | ||||
|         /* Try and determine the DB from the invitation key STRING*/ | ||||
|  | ||||
							
								
								
									
										57
									
								
								app/Http/Middleware/VendorLocale.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								app/Http/Middleware/VendorLocale.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | ||||
| <?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\Http\Middleware; | ||||
| 
 | ||||
| use Closure; | ||||
| use Illuminate\Http\Request; | ||||
| use Illuminate\Support\Facades\App; | ||||
| use Illuminate\Support\Facades\Auth; | ||||
| 
 | ||||
| class VendorLocale | ||||
| { | ||||
|     /** | ||||
|      * Handle an incoming request. | ||||
|      * | ||||
|      * @param  Request  $request | ||||
|      * @param Closure $next | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function handle($request, Closure $next) | ||||
|     { | ||||
| 
 | ||||
|         if (auth()->guard('contact')->check()) { | ||||
|             auth()->guard('contact')->logout(); | ||||
|             $request->session()->invalidate(); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         /*LOCALE SET */ | ||||
|         if ($request->has('lang')) { | ||||
|             $locale = $request->input('lang'); | ||||
|             App::setLocale($locale); | ||||
|         } elseif (auth()->guard('vendor')->user()) { | ||||
|             App::setLocale(auth()->guard('vendor')->user()->company->locale()); | ||||
|         } elseif (auth()->user()) { | ||||
| 
 | ||||
|             try{ | ||||
|                 App::setLocale(auth()->user()->company()->getLocale()); | ||||
|             } | ||||
|             catch(\Exception $e){ | ||||
|             } | ||||
| 
 | ||||
|         } else { | ||||
|             App::setLocale(config('ninja.i18n.locale')); | ||||
|         } | ||||
| 
 | ||||
|         return $next($request); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										54
									
								
								app/Http/Requests/Account/UpdateAccountRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								app/Http/Requests/Account/UpdateAccountRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| <?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\Http\Requests\Account; | ||||
| 
 | ||||
| use App\Http\Requests\Request; | ||||
| use App\Http\ValidationRules\Account\BlackListRule; | ||||
| use App\Http\ValidationRules\Account\EmailBlackListRule; | ||||
| use App\Http\ValidationRules\NewUniqueUserRule; | ||||
| use App\Utils\Ninja; | ||||
| 
 | ||||
| class UpdateAccountRequest extends Request | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() | ||||
|     { | ||||
|         return (auth()->user()->isAdmin() || auth()->user()->isOwner()) && (int)$this->account->id === auth()->user()->account_id; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the validation rules that apply to the request. | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     public function rules() | ||||
|     { | ||||
|         return [ | ||||
|             'set_react_as_default_ap' => 'required|bail|bool' | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     /* Only allow single field to update account table */ | ||||
|     protected function prepareForValidation() | ||||
|     { | ||||
|         $input = $this->all(); | ||||
| 
 | ||||
|         $cleaned_input = array_intersect_key( $input, array_flip(['set_react_as_default_ap'])); | ||||
| 
 | ||||
|         $this->replace($cleaned_input); | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -23,7 +23,7 @@ class ShowInvoiceRequest extends Request | ||||
|      */ | ||||
|     public function authorize() : bool | ||||
|     { | ||||
|         return auth()->guard('contact')->user()->client_id === $this->invoice->client_id | ||||
|         return (int)auth()->guard('contact')->user()->client_id === (int)$this->invoice->client_id | ||||
|             &&  auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_INVOICES; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -27,9 +27,8 @@ class CreatePaymentMethodRequest extends FormRequest | ||||
|                 $available_methods[] = $method['gateway_type_id']; | ||||
|             }); | ||||
| 
 | ||||
|         if (in_array($this->query('method'), $available_methods)) { | ||||
|             return true; | ||||
|         } | ||||
|         if (in_array($this->query('method'), $available_methods))  | ||||
|             return true;         | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| @ -19,7 +19,7 @@ class ShowQuoteRequest extends FormRequest | ||||
| { | ||||
|     public function authorize() | ||||
|     { | ||||
|          return auth()->guard('contact')->user()->client->id === $this->quote->client_id | ||||
|          return auth()->guard('contact')->user()->client->id === (int)$this->quote->client_id | ||||
|              && auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_QUOTES; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -39,21 +39,28 @@ class Checkout3dsRequest extends FormRequest | ||||
|     public function getCompany() | ||||
|     { | ||||
|         MultiDB::findAndSetDbByCompanyKey($this->company_key); | ||||
| 
 | ||||
|         return Company::where('company_key', $this->company_key)->first(); | ||||
|     } | ||||
| 
 | ||||
|     public function getCompanyGateway() | ||||
|     { | ||||
|         MultiDB::findAndSetDbByCompanyKey($this->company_key); | ||||
| 
 | ||||
|         return CompanyGateway::find($this->decodePrimaryKey($this->company_gateway_id)); | ||||
|     } | ||||
| 
 | ||||
|     public function getPaymentHash() | ||||
|     { | ||||
|         MultiDB::findAndSetDbByCompanyKey($this->company_key); | ||||
| 
 | ||||
|         return PaymentHash::where('hash', $this->hash)->first(); | ||||
|     } | ||||
| 
 | ||||
|     public function getClient() | ||||
|     { | ||||
|         return Client::find($this->getPaymentHash()->data->client_id); | ||||
|         MultiDB::findAndSetDbByCompanyKey($this->company_key); | ||||
| 
 | ||||
|         return Client::withTrashed()->find($this->getPaymentHash()->data->client_id); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -38,7 +38,7 @@ class StorePurchaseOrderRequest extends Request | ||||
|     { | ||||
|         $rules = []; | ||||
| 
 | ||||
|         $rules['vendor_id'] = 'required'; | ||||
|         $rules['vendor_id'] = 'bail|required|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; | ||||
| 
 | ||||
|         $rules['number'] = ['nullable', Rule::unique('purchase_orders')->where('company_id', auth()->user()->company()->id)]; | ||||
|         $rules['discount']  = 'sometimes|numeric'; | ||||
|  | ||||
| @ -0,0 +1,31 @@ | ||||
| <?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\Http\Requests\VendorPortal\PurchaseOrders; | ||||
| 
 | ||||
| use App\Http\ViewComposers\PortalComposer; | ||||
| use Illuminate\Foundation\Http\FormRequest; | ||||
| 
 | ||||
| class ProcessPurchaseOrdersInBulkRequest extends FormRequest | ||||
| { | ||||
|     public function authorize() | ||||
|     { | ||||
|         return auth()->guard('vendor')->user()->vendor->company->enabled_modules & PortalComposer::MODULE_PURCHASE_ORDERS; | ||||
|     } | ||||
| 
 | ||||
|     public function rules() | ||||
|     { | ||||
|         return [ | ||||
|             'purchase_orders' => ['array'], | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,29 @@ | ||||
| <?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\Http\Requests\VendorPortal\PurchaseOrders; | ||||
| 
 | ||||
| use App\Http\Requests\Request; | ||||
| use App\Http\ViewComposers\PortalComposer; | ||||
| 
 | ||||
| class ShowPurchaseOrderRequest extends Request | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() : bool | ||||
|     { | ||||
|         return (int)auth()->guard('vendor')->user()->vendor_id === (int)$this->purchase_order->vendor_id | ||||
|             &&  auth()->guard('vendor')->user()->company->enabled_modules & PortalComposer::MODULE_PURCHASE_ORDERS; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,29 @@ | ||||
| <?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\Http\Requests\VendorPortal\PurchaseOrders; | ||||
| 
 | ||||
| use App\Http\Requests\Request; | ||||
| use App\Http\ViewComposers\PortalComposer; | ||||
| 
 | ||||
| class ShowPurchaseOrdersRequest extends Request | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() : bool | ||||
|     { | ||||
|         return auth()->guard('vendor')->user()->company->enabled_modules & PortalComposer::MODULE_PURCHASE_ORDERS; | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -70,7 +70,7 @@ class CreateEntityPdf implements ShouldQueue | ||||
|      * | ||||
|      * @param $invitation | ||||
|      */ | ||||
|     public function __construct($invitation, $disk = 'public') | ||||
|     public function __construct($invitation, $disk = null) | ||||
|     { | ||||
|         $this->invitation = $invitation; | ||||
| 
 | ||||
| @ -99,7 +99,7 @@ class CreateEntityPdf implements ShouldQueue | ||||
|         $this->client = $invitation->contact->client; | ||||
|         $this->client->load('company'); | ||||
|          | ||||
|         $this->disk = Ninja::isHosted() ? config('filesystems.default') : $disk; | ||||
|         $this->disk = $disk ?? config('filesystems.default'); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -47,7 +47,7 @@ class ClientLedgerBalanceUpdate implements ShouldQueue | ||||
|      */ | ||||
|     public function handle() :void | ||||
|     { | ||||
|         nlog("Updating company ledger for client ". $this->client->id); | ||||
|         // nlog("Updating company ledger for client ". $this->client->id);
 | ||||
| 
 | ||||
|         MultiDB::setDb($this->company->db); | ||||
|          | ||||
|  | ||||
							
								
								
									
										102
									
								
								app/Jobs/PurchaseOrder/PurchaseOrderEmail.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								app/Jobs/PurchaseOrder/PurchaseOrderEmail.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,102 @@ | ||||
| <?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\Jobs\PurchaseOrder; | ||||
| 
 | ||||
| use App\Events\PurchaseOrder\PurchaseOrderWasEmailed; | ||||
| use App\Jobs\Mail\NinjaMailerJob; | ||||
| use App\Jobs\Mail\NinjaMailerObject; | ||||
| use App\Libraries\MultiDB; | ||||
| use App\Mail\Engine\PurchaseOrderEmailEngine; | ||||
| use App\Mail\VendorTemplateEmail; | ||||
| use App\Models\Company; | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Utils\Ninja; | ||||
| use Exception; | ||||
| use Illuminate\Bus\Queueable; | ||||
| use Illuminate\Contracts\Queue\ShouldQueue; | ||||
| use Illuminate\Foundation\Bus\Dispatchable; | ||||
| use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| use Illuminate\Support\Facades\App; | ||||
| 
 | ||||
| class PurchaseOrderEmail implements ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
|     public PurchaseOrder $purchase_order; | ||||
| 
 | ||||
|     public Company $company; | ||||
| 
 | ||||
|     public $template_data; | ||||
| 
 | ||||
|     public $tries = 1; | ||||
| 
 | ||||
|     public function __construct(PurchaseOrder $purchase_order, Company $company, $template_data = null) | ||||
|     { | ||||
|         $this->purchase_order = $purchase_order; | ||||
|         $this->company = $company; | ||||
|         $this->template_data = $template_data; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Execute the job. | ||||
|      * | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function handle() | ||||
|     { | ||||
|         MultiDB::setDb($this->company->db); | ||||
| 
 | ||||
|         $this->purchase_order->last_sent_date = now(); | ||||
| 
 | ||||
|         $this->purchase_order->invitations->load('contact.vendor.country', 'purchase_order.vendor.country', 'purchase_order.company')->each(function ($invitation) { | ||||
| 
 | ||||
|         /* Don't fire emails if the company is disabled */ | ||||
|         if ($this->company->is_disabled)  | ||||
|             return true; | ||||
|          | ||||
|             /* Set DB */ | ||||
|             MultiDB::setDB($this->company->db); | ||||
| 
 | ||||
|             App::forgetInstance('translator'); | ||||
|             $t = app('translator'); | ||||
|             App::setLocale($invitation->contact->preferredLocale()); | ||||
|             $t->replace(Ninja::transformTranslations($this->company->settings)); | ||||
| 
 | ||||
|             /* Mark entity sent */ | ||||
|             $invitation->purchase_order->service()->markSent()->save(); | ||||
|              | ||||
|             $email_builder = (new PurchaseOrderEmailEngine($invitation, 'purchase_order', $this->template_data))->build(); | ||||
| 
 | ||||
|             $nmo = new NinjaMailerObject; | ||||
|             $nmo->mailable = new VendorTemplateEmail($email_builder, $invitation->contact, $invitation); | ||||
|             $nmo->company = $this->company; | ||||
|             $nmo->settings = $this->company->settings; | ||||
|             $nmo->to_user = $invitation->contact; | ||||
|             $nmo->entity_string = 'purchase_order'; | ||||
|             $nmo->invitation = $invitation; | ||||
|             $nmo->reminder_template = 'purchase_order'; | ||||
|             $nmo->entity = $invitation->purchase_order; | ||||
|              | ||||
|             NinjaMailerJob::dispatchNow($nmo); | ||||
| 
 | ||||
| 
 | ||||
|         }); | ||||
| 
 | ||||
|         if ($this->purchase_order->invitations->count() >= 1) { | ||||
|             event(new PurchaseOrderWasEmailed($this->purchase_order->invitations->first(), $this->purchase_order->invitations->first()->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										126
									
								
								app/Jobs/PurchaseOrder/ZipPurchaseOrders.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								app/Jobs/PurchaseOrder/ZipPurchaseOrders.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,126 @@ | ||||
| <?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\Jobs\PurchaseOrder; | ||||
| 
 | ||||
| use App\Jobs\Entity\CreateEntityPdf; | ||||
| use App\Jobs\Mail\NinjaMailerJob; | ||||
| use App\Jobs\Mail\NinjaMailerObject; | ||||
| use App\Jobs\Util\UnlinkFile; | ||||
| use App\Jobs\Vendor\CreatePurchaseOrderPdf; | ||||
| use App\Libraries\MultiDB; | ||||
| use App\Mail\DownloadInvoices; | ||||
| use App\Mail\DownloadPurchaseOrders; | ||||
| use App\Models\Company; | ||||
| use App\Models\User; | ||||
| use App\Utils\TempFile; | ||||
| use Illuminate\Bus\Queueable; | ||||
| use Illuminate\Contracts\Queue\ShouldQueue; | ||||
| use Illuminate\Foundation\Bus\Dispatchable; | ||||
| use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| use Illuminate\Support\Facades\Mail; | ||||
| use Illuminate\Support\Facades\Storage; | ||||
| use ZipArchive; | ||||
| 
 | ||||
| class ZipPurchaseOrders implements ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
|     public $purchase_orders; | ||||
| 
 | ||||
|     private $company; | ||||
| 
 | ||||
|     private $user; | ||||
| 
 | ||||
|     public $settings; | ||||
| 
 | ||||
|     public $tries = 1; | ||||
| 
 | ||||
|     /** | ||||
|      * @param $purchase_orders | ||||
|      * @param Company $company | ||||
|      * @param $email | ||||
|      * @deprecated confirm to be deleted | ||||
|      * Create a new job instance. | ||||
|      * | ||||
|      */ | ||||
|     public function __construct($purchase_orders, Company $company, User $user) | ||||
|     { | ||||
|         $this->purchase_orders = $purchase_orders; | ||||
| 
 | ||||
|         $this->company = $company; | ||||
| 
 | ||||
|         $this->user = $user; | ||||
| 
 | ||||
|         $this->settings = $company->settings; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Execute the job. | ||||
|      * | ||||
|      * @return void | ||||
|      * @throws \ZipStream\Exception\FileNotFoundException | ||||
|      * @throws \ZipStream\Exception\FileNotReadableException | ||||
|      * @throws \ZipStream\Exception\OverflowException | ||||
|      */ | ||||
|      | ||||
|     public function handle() | ||||
|     { | ||||
|         MultiDB::setDb($this->company->db); | ||||
| 
 | ||||
|         # create new zip object
 | ||||
|         $zipFile = new \PhpZip\ZipFile(); | ||||
|         $file_name = date('Y-m-d').'_'.str_replace(' ', '_', trans('texts.invoices')).'.zip'; | ||||
|         $invitation = $this->purchase_orders->first()->invitations->first(); | ||||
|         $path = $this->purchase_orders->first()->vendor->purchase_order_filepath($invitation); | ||||
| 
 | ||||
|         $this->purchase_orders->each(function ($purchase_order){ | ||||
| 
 | ||||
|             CreatePurchaseOrderPdf::dispatchNow($purchase_order->invitations()->first()); | ||||
| 
 | ||||
|         }); | ||||
| 
 | ||||
|         try{ | ||||
|              | ||||
|             foreach ($this->purchase_orders as $purchase_order) { | ||||
|          | ||||
|                 $file = $purchase_order->service()->getPurchaseOrderPdf(); | ||||
|                 $zip_file_name = basename($file); | ||||
|                 $zipFile->addFromString($zip_file_name, Storage::get($file)); | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|             Storage::put($path.$file_name, $zipFile->outputAsString()); | ||||
| 
 | ||||
|             $nmo = new NinjaMailerObject; | ||||
|             $nmo->mailable = new DownloadPurchaseOrders(Storage::url($path.$file_name), $this->company); | ||||
|             $nmo->to_user = $this->user; | ||||
|             $nmo->settings = $this->settings; | ||||
|             $nmo->company = $this->company; | ||||
|              | ||||
|             NinjaMailerJob::dispatch($nmo); | ||||
|              | ||||
|             UnlinkFile::dispatch(config('filesystems.default'), $path.$file_name)->delay(now()->addHours(1)); | ||||
|              | ||||
| 
 | ||||
|         } | ||||
|         catch(\PhpZip\Exception\ZipException $e){ | ||||
|             nlog("could not make zip => ". $e->getMessage()); | ||||
|         } | ||||
|         finally{ | ||||
|             $zipFile->close(); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										4
									
								
								app/Jobs/Vendor/CreatePurchaseOrderPdf.php
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								app/Jobs/Vendor/CreatePurchaseOrderPdf.php
									
									
									
									
										vendored
									
									
								
							| @ -70,7 +70,7 @@ class CreatePurchaseOrderPdf implements ShouldQueue | ||||
|      * | ||||
|      * @param $invitation | ||||
|      */ | ||||
|     public function __construct($invitation, $disk = 'public') | ||||
|     public function __construct($invitation, $disk = null) | ||||
|     { | ||||
|         $this->invitation = $invitation; | ||||
|         $this->company = $invitation->company; | ||||
| @ -83,7 +83,7 @@ class CreatePurchaseOrderPdf implements ShouldQueue | ||||
|         $this->vendor = $invitation->contact->vendor; | ||||
|         $this->vendor->load('company'); | ||||
|          | ||||
|         $this->disk = Ninja::isHosted() ? config('filesystems.default') : $disk; | ||||
|         $this->disk = $disk ?? config('filesystems.default'); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -29,6 +29,8 @@ class OAuth | ||||
|     const SOCIAL_LINKEDIN = 4; | ||||
|     const SOCIAL_TWITTER = 5; | ||||
|     const SOCIAL_BITBUCKET = 6; | ||||
|     const SOCIAL_MICROSOFT = 7; | ||||
|     const SOCIAL_APPLE = 8; | ||||
| 
 | ||||
|     /** | ||||
|      * @param Socialite $user | ||||
| @ -38,8 +40,8 @@ class OAuth | ||||
|     { | ||||
|         /** 1. Ensure user arrives on the correct provider **/ | ||||
|         $query = [ | ||||
|             'oauth_user_id' =>$socialite_user->getId(), | ||||
|             'oauth_provider_id'=>$provider, | ||||
|             'oauth_user_id' => $socialite_user->getId(), | ||||
|             'oauth_provider_id' => $provider, | ||||
|         ]; | ||||
| 
 | ||||
|         if ($user = MultiDB::hasUser($query)) { | ||||
| @ -54,12 +56,12 @@ class OAuth | ||||
|     { | ||||
|         $name = trim($name); | ||||
|         $last_name = (strpos($name, ' ') === false) ? '' : preg_replace('#.*\s([\w-]*)$#', '$1', $name); | ||||
|         $first_name = trim(preg_replace('#'.preg_quote($last_name, '/').'#', '', $name)); | ||||
|         $first_name = trim(preg_replace('#' . preg_quote($last_name, '/') . '#', '', $name)); | ||||
| 
 | ||||
|         return [$first_name, $last_name]; | ||||
|     } | ||||
| 
 | ||||
|     public static function providerToString(int $social_provider) : string | ||||
|     public static function providerToString(int $social_provider): string | ||||
|     { | ||||
|         switch ($social_provider) { | ||||
|             case SOCIAL_GOOGLE: | ||||
| @ -74,10 +76,14 @@ class OAuth | ||||
|                 return 'twitter'; | ||||
|             case SOCIAL_BITBUCKET: | ||||
|                 return 'bitbucket'; | ||||
|             case SOCIAL_MICROSOFT: | ||||
|                 return 'microsoft'; | ||||
|             case SOCIAL_APPLE: | ||||
|                 return 'apple'; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static function providerToInt(string $social_provider) : int | ||||
|     public static function providerToInt(string $social_provider): int | ||||
|     { | ||||
|         switch ($social_provider) { | ||||
|             case 'google': | ||||
| @ -92,6 +98,10 @@ class OAuth | ||||
|                 return SOCIAL_TWITTER; | ||||
|             case 'bitbucket': | ||||
|                 return SOCIAL_BITBUCKET; | ||||
|             case 'microsoft': | ||||
|                 return SOCIAL_MICROSOFT; | ||||
|             case 'apple': | ||||
|                 return SOCIAL_APPLE; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -103,7 +113,6 @@ class OAuth | ||||
|                 $this->provider_id = self::SOCIAL_GOOGLE; | ||||
| 
 | ||||
|                 return $this; | ||||
| 
 | ||||
|             default: | ||||
|                 return null; | ||||
|                 break; | ||||
|  | ||||
| @ -49,7 +49,9 @@ class InvitationViewedListener implements ShouldQueue | ||||
| 
 | ||||
|         if($entity_name == 'recurringInvoice') | ||||
|             return; | ||||
| 
 | ||||
|         elseif($entity_name == 'purchaseOrder') | ||||
|             $entity_name = 'purchase_order'; | ||||
|          | ||||
|         $nmo = new NinjaMailerObject; | ||||
|         $nmo->mailable = new NinjaMailer( (new EntityViewedObject($invitation, $entity_name))->build() ); | ||||
|         $nmo->company = $invitation->company; | ||||
| @ -60,6 +62,8 @@ class InvitationViewedListener implements ShouldQueue | ||||
|             $entity_viewed = "{$entity_name}_viewed"; | ||||
|             $entity_viewed_all = "{$entity_name}_viewed_all"; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|             $methods = $this->findUserNotificationTypes($invitation, $company_user, $entity_name, ['all_notifications', $entity_viewed, $entity_viewed_all]); | ||||
| 
 | ||||
|             if (($key = array_search('mail', $methods)) !== false) { | ||||
|  | ||||
| @ -0,0 +1,61 @@ | ||||
| <?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\Listeners\PurchaseOrder; | ||||
| 
 | ||||
| use App\Libraries\MultiDB; | ||||
| use App\Models\Activity; | ||||
| use App\Repositories\ActivityRepository; | ||||
| use Illuminate\Contracts\Queue\ShouldQueue; | ||||
| use stdClass; | ||||
| 
 | ||||
| class PurchaseOrderAcceptedActivity implements ShouldQueue | ||||
| { | ||||
|     protected $activity_repo; | ||||
| 
 | ||||
|     public $delay = 5; | ||||
|      | ||||
|     /** | ||||
|      * Create the event listener. | ||||
|      * | ||||
|      * @param ActivityRepository $activity_repo | ||||
|      */ | ||||
|     public function __construct(ActivityRepository $activity_repo) | ||||
|     { | ||||
|         $this->activity_repo = $activity_repo; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Handle the event. | ||||
|      * | ||||
|      * @param  object  $event | ||||
|      * @return void | ||||
|      */ | ||||
|     public function handle($event) | ||||
|     { | ||||
|         MultiDB::setDb($event->company->db); | ||||
| 
 | ||||
|         $fields = new stdClass; | ||||
| 
 | ||||
|         $user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->purchase_order->user_id; | ||||
| 
 | ||||
|         $event->purchase_order->service()->markSent()->save(); | ||||
| 
 | ||||
|         $fields->user_id = $user_id; | ||||
|         $fields->company_id = $event->purchase_order->company_id; | ||||
|         $fields->activity_type_id = Activity::ACCEPT_PURCHASE_ORDER; | ||||
|         $fields->vendor_id = $event->purchase_order->vendor_id; | ||||
|         $fields->vendor_contact_id = $event->contact->id; | ||||
|         $fields->purchase_order_id = $event->purchase_order->id; | ||||
| 
 | ||||
|         $this->activity_repo->save($fields, $event->purchase_order, $event->event_vars); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,79 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Quote Ninja (https://quoteninja.com). | ||||
|  * | ||||
|  * @link https://github.com/quoteninja/quoteninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2022. Quote Ninja LLC (https://quoteninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Listeners\PurchaseOrder; | ||||
| 
 | ||||
| use App\Jobs\Mail\NinjaMailer; | ||||
| use App\Jobs\Mail\NinjaMailerJob; | ||||
| use App\Jobs\Mail\NinjaMailerObject; | ||||
| use App\Libraries\MultiDB; | ||||
| use App\Mail\Admin\EntityCreatedObject; | ||||
| use App\Mail\Admin\PurchaseOrderAcceptedObject; | ||||
| use App\Notifications\Admin\EntitySentNotification; | ||||
| use App\Utils\Traits\Notifications\UserNotifies; | ||||
| use Illuminate\Contracts\Queue\ShouldQueue; | ||||
| 
 | ||||
| class PurchaseOrderAcceptedNotification implements ShouldQueue | ||||
| { | ||||
|     use UserNotifies; | ||||
| 
 | ||||
|     public $delay = 5; | ||||
|      | ||||
|     public function __construct() | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Handle the event. | ||||
|      * | ||||
|      * @param  object  $event | ||||
|      * @return void | ||||
|      */ | ||||
|     public function handle($event) | ||||
|     { | ||||
|         MultiDB::setDb($event->company->db); | ||||
| 
 | ||||
|         $first_notification_sent = true; | ||||
| 
 | ||||
|         $purchase_order = $event->purchase_order; | ||||
| 
 | ||||
|         $nmo = new NinjaMailerObject; | ||||
|         $nmo->mailable = new NinjaMailer( (new PurchaseOrderAcceptedObject($purchase_order, $event->company))->build() ); | ||||
|         $nmo->company = $event->company; | ||||
|         $nmo->settings = $event->company->settings; | ||||
| 
 | ||||
|         /* We loop through each user and determine whether they need to be notified */ | ||||
|         foreach ($event->company->company_users as $company_user) { | ||||
| 
 | ||||
|             /* The User */ | ||||
|             $user = $company_user->user; | ||||
| 
 | ||||
|             if(!$user) | ||||
|                 continue; | ||||
| 
 | ||||
|             /* Returns an array of notification methods */ | ||||
|             $methods = $this->findUserNotificationTypes($purchase_order->invitations()->first(), $company_user, 'purchase_order', ['all_notifications', 'purchase_order_accepted', 'purchase_order_accepted_all']); | ||||
| 
 | ||||
|             /* If one of the methods is email then we fire the EntitySentMailer */ | ||||
|             if (($key = array_search('mail', $methods)) !== false) { | ||||
|                 unset($methods[$key]); | ||||
| 
 | ||||
|                 $nmo->to_user = $user; | ||||
| 
 | ||||
|                 NinjaMailerJob::dispatch($nmo); | ||||
|                  | ||||
|                 /* This prevents more than one notification being sent */ | ||||
|                 $first_notification_sent = false; | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -65,7 +65,12 @@ class EntityViewedObject | ||||
| 
 | ||||
|     private function getAmount() | ||||
|     { | ||||
|         return Number::formatMoney($this->entity->amount, $this->entity->client); | ||||
|         if($this->entity->client) | ||||
|             $currency_entity = $this->entity->client; | ||||
|         else | ||||
|             $currency_entity = $this->company; | ||||
| 
 | ||||
|         return Number::formatMoney($this->entity->amount, $currency_entity); | ||||
|     } | ||||
| 
 | ||||
|     private function getSubject() | ||||
| @ -82,7 +87,10 @@ class EntityViewedObject | ||||
| 
 | ||||
|     private function getData() | ||||
|     { | ||||
|         $settings = $this->entity->client->getMergedSettings(); | ||||
|         if($this->entity->client) | ||||
|             $settings = $this->entity->client->getMergedSettings(); | ||||
|         else | ||||
|             $settings = $this->company->settings;  | ||||
| 
 | ||||
|         $data = [ | ||||
|             'title' => $this->getSubject(), | ||||
|  | ||||
							
								
								
									
										103
									
								
								app/Mail/Admin/PurchaseOrderAcceptedObject.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								app/Mail/Admin/PurchaseOrderAcceptedObject.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | ||||
| <?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\Mail\Admin; | ||||
| 
 | ||||
| use App\Libraries\MultiDB; | ||||
| use App\Models\Company; | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Utils\Ninja; | ||||
| use App\Utils\Number; | ||||
| use Illuminate\Support\Facades\App; | ||||
| use stdClass; | ||||
| 
 | ||||
| class PurchaseOrderAcceptedObject | ||||
| { | ||||
| 
 | ||||
|     public $purchase_order; | ||||
| 
 | ||||
|     public $company; | ||||
| 
 | ||||
|     public $settings; | ||||
| 
 | ||||
|     public function __construct(PurchaseOrder $purchase_order, Company $company) | ||||
|     { | ||||
|         $this->purchase_order = $purchase_order; | ||||
|         $this->company = $company; | ||||
|     } | ||||
| 
 | ||||
|     public function build() | ||||
|     { | ||||
|         MultiDB::setDb($this->company->db); | ||||
| 
 | ||||
|         if(!$this->purchase_order) | ||||
|             return; | ||||
| 
 | ||||
|         App::forgetInstance('translator'); | ||||
|         /* Init a new copy of the translator*/ | ||||
|         $t = app('translator'); | ||||
|         /* Set the locale*/ | ||||
|         App::setLocale($this->company->getLocale()); | ||||
|         /* Set customized translations _NOW_ */ | ||||
|         $t->replace(Ninja::transformTranslations($this->company->settings)); | ||||
| 
 | ||||
|         $mail_obj = new stdClass; | ||||
|         $mail_obj->amount = $this->getAmount(); | ||||
|         $mail_obj->subject = $this->getSubject(); | ||||
|         $mail_obj->data = $this->getData(); | ||||
|         $mail_obj->markdown = 'email.admin.generic'; | ||||
|         $mail_obj->tag = $this->company->company_key; | ||||
| 
 | ||||
|         return $mail_obj; | ||||
|     } | ||||
| 
 | ||||
|     private function getAmount() | ||||
|     { | ||||
|         return Number::formatMoney($this->purchase_order->amount, $this->company); | ||||
|     } | ||||
| 
 | ||||
|     private function getSubject() | ||||
|     { | ||||
|         return | ||||
|             ctrans( | ||||
|                 "texts.notification_purchase_order_accepted_subject", | ||||
|                 [ | ||||
|                         'vendor' => $this->purchase_order->vendor->present()->name(), | ||||
|                         'purchase_order' => $this->purchase_order->number, | ||||
|                     ] | ||||
|             ); | ||||
|     } | ||||
| 
 | ||||
|     private function getData() | ||||
|     { | ||||
|         $settings = $this->company->settings; | ||||
| 
 | ||||
|         $data = [ | ||||
|             'title' => $this->getSubject(), | ||||
|             'message' => ctrans( | ||||
|                 "texts.notification_purchase_order_accepted", | ||||
|                 [ | ||||
|                     'amount' => $this->getAmount(), | ||||
|                     'vendor' => $this->purchase_order->vendor->present()->name(), | ||||
|                     'purchase_order' => $this->purchase_order->number, | ||||
|                 ] | ||||
|             ), | ||||
|             'url' => $this->purchase_order->invitations->first()->getAdminLink(), | ||||
|             'button' => ctrans("texts.view_purchase_order"), | ||||
|             'signature' => $settings->email_signature, | ||||
|             'logo' => $this->company->present()->logo(), | ||||
|             'settings' => $settings, | ||||
|             'whitelabel' => $this->company->account->isPaid() ? true : false, | ||||
|         ]; | ||||
| 
 | ||||
|         return $data; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										56
									
								
								app/Mail/DownloadPurchaseOrders.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								app/Mail/DownloadPurchaseOrders.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| <?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\Mail; | ||||
| 
 | ||||
| use App\Models\Company; | ||||
| use Illuminate\Bus\Queueable; | ||||
| use Illuminate\Mail\Mailable; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| use Illuminate\Support\Facades\App; | ||||
| 
 | ||||
| class DownloadPurchaseOrders extends Mailable | ||||
| { | ||||
|     // use Queueable, SerializesModels;
 | ||||
| 
 | ||||
|     public $file_path; | ||||
| 
 | ||||
|     public $company; | ||||
| 
 | ||||
|     public function __construct($file_path, Company $company) | ||||
|     { | ||||
|         $this->file_path = $file_path; | ||||
| 
 | ||||
|         $this->company = $company; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Build the message. | ||||
|      */ | ||||
|     public function build() | ||||
|     { | ||||
| 
 | ||||
|         App::setLocale($this->company->getLocale()); | ||||
| 
 | ||||
|         return $this->from(config('mail.from.address'), config('mail.from.name')) | ||||
|             ->subject(ctrans('texts.download_files')) | ||||
|             ->text('email.admin.download_invoices_text', [ | ||||
|                 'url' => $this->file_path, | ||||
|             ]) | ||||
|             ->view('email.admin.download_purchase_orders', [ | ||||
|                 'url' => $this->file_path, | ||||
|                 'logo' => $this->company->present()->logo, | ||||
|                 'whitelabel' => $this->company->account->isPaid() ? true : false, | ||||
|                 'settings' => $this->company->settings, | ||||
|                 'greeting' => $this->company->present()->name(), | ||||
|             ]); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										207
									
								
								app/Mail/Engine/PurchaseOrderEmailEngine.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								app/Mail/Engine/PurchaseOrderEmailEngine.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,207 @@ | ||||
| <?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\Mail\Engine; | ||||
| 
 | ||||
| use App\DataMapper\EmailTemplateDefaults; | ||||
| use App\Jobs\Entity\CreateEntityPdf; | ||||
| use App\Models\Account; | ||||
| use App\Models\Expense; | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Models\Task; | ||||
| use App\Models\Vendor; | ||||
| use App\Models\VendorContact; | ||||
| use App\Utils\HtmlEngine; | ||||
| use App\Utils\Ninja; | ||||
| use App\Utils\Number; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use App\Utils\VendorHtmlEngine; | ||||
| use Illuminate\Support\Facades\App; | ||||
| use Illuminate\Support\Facades\Lang; | ||||
| 
 | ||||
| class PurchaseOrderEmailEngine extends BaseEmailEngine | ||||
| { | ||||
|     use MakesHash; | ||||
| 
 | ||||
|     public $invitation; | ||||
| 
 | ||||
|     public Vendor $vendor; | ||||
| 
 | ||||
|     public PurchaseOrder $purchase_order; | ||||
| 
 | ||||
|     public $contact; | ||||
| 
 | ||||
|     public $reminder_template; | ||||
| 
 | ||||
|     public $template_data; | ||||
| 
 | ||||
|     public function __construct($invitation, $reminder_template, $template_data) | ||||
|     { | ||||
|         $this->invitation = $invitation; | ||||
|         $this->reminder_template = $reminder_template; //'purchase_order'
 | ||||
|         $this->vendor = $invitation->contact->vendor; | ||||
|         $this->purchase_order = $invitation->purchase_order; | ||||
|         $this->contact = $invitation->contact; | ||||
|         $this->template_data = $template_data; | ||||
|     } | ||||
| 
 | ||||
|     public function build() | ||||
|     { | ||||
| 
 | ||||
|         App::forgetInstance('translator'); | ||||
|         $t = app('translator'); | ||||
|         $t->replace(Ninja::transformTranslations($this->vendor->company->settings)); | ||||
| 
 | ||||
|         if (is_array($this->template_data) &&  array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) { | ||||
|             $body_template = $this->template_data['body']; | ||||
|         } elseif (strlen($this->vendor->getSetting('email_template_'.$this->reminder_template)) > 0) { | ||||
|             $body_template = $this->vendor->getSetting('email_template_'.$this->reminder_template); | ||||
|         } else { | ||||
|             $body_template = EmailTemplateDefaults::getDefaultTemplate('email_template_'.$this->reminder_template, $this->vendor->company->locale()); | ||||
|         } | ||||
| 
 | ||||
|         /* Use default translations if a custom message has not been set*/ | ||||
|         if (iconv_strlen($body_template) == 0) { | ||||
|             $body_template = trans( | ||||
|                 'texts.invoice_message', | ||||
|                 [ | ||||
|                     'invoice' => $this->purchase_order->number, | ||||
|                     'company' => $this->purchase_order->company->present()->name(), | ||||
|                     'amount' => Number::formatMoney($this->purchase_order->balance, $this->vendor), | ||||
|                 ], | ||||
|                 null, | ||||
|                 $this->vendor->company->locale() | ||||
|             ); | ||||
| 
 | ||||
|             $body_template .= '<div class="center">$view_button</div>'; | ||||
| 
 | ||||
|         } | ||||
|         $text_body = trans( | ||||
|                 'texts.purchase_order_message', | ||||
|                 [ | ||||
|                     'purchase_order' => $this->purchase_order->number, | ||||
|                     'company' => $this->purchase_order->company->present()->name(), | ||||
|                     'amount' => Number::formatMoney($this->purchase_order->balance, $this->vendor), | ||||
|                 ], | ||||
|                 null, | ||||
|                 $this->vendor->company->locale() | ||||
|             ) . "\n\n" . $this->invitation->getLink(); | ||||
| 
 | ||||
|         if (is_array($this->template_data) &&  array_key_exists('subject', $this->template_data) && strlen($this->template_data['subject']) > 0) { | ||||
|             $subject_template = $this->template_data['subject']; | ||||
|         } elseif (strlen($this->vendor->getSetting('email_subject_'.$this->reminder_template)) > 0) { | ||||
|             $subject_template = $this->vendor->getSetting('email_subject_'.$this->reminder_template); | ||||
|         } else { | ||||
|             $subject_template = EmailTemplateDefaults::getDefaultTemplate('email_subject_'.$this->reminder_template, $this->vendor->company->locale()); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         if (iconv_strlen($subject_template) == 0) { | ||||
|             $subject_template = trans( | ||||
|                 'texts.purchase_order_subject', | ||||
|                 [ | ||||
|                     'number' => $this->purchase_order->number, | ||||
|                     'account' => $this->purchase_order->company->present()->name(), | ||||
|                 ], | ||||
|                 null, | ||||
|                 $this->vendor->company->locale() | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         $this->setTemplate($this->vendor->getSetting('email_style')) | ||||
|             ->setContact($this->contact) | ||||
|             ->setVariables((new VendorHtmlEngine($this->invitation))->makeValues())//move make values into the htmlengine
 | ||||
|             ->setSubject($subject_template) | ||||
|             ->setBody($body_template) | ||||
|             ->setFooter("<a href='{$this->invitation->getLink()}'>".ctrans('texts.view_purchase_order').'</a>') | ||||
|             ->setViewLink($this->invitation->getLink()) | ||||
|             ->setViewText(ctrans('texts.view_purchase_order')) | ||||
|             ->setInvitation($this->invitation) | ||||
|             ->setTextBody($text_body); | ||||
| 
 | ||||
|         if ($this->vendor->getSetting('pdf_email_attachment') !== false && $this->purchase_order->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) { | ||||
| 
 | ||||
|             if(Ninja::isHosted()) | ||||
|                 $this->setAttachments([$this->purchase_order->pdf_file_path($this->invitation, 'url', true)]); | ||||
|             else | ||||
|                 $this->setAttachments([$this->purchase_order->pdf_file_path($this->invitation)]); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         //attach third party documents
 | ||||
|         if($this->vendor->getSetting('document_email_attachment') !== false && $this->purchase_order->company->account->hasFeature(Account::FEATURE_DOCUMENTS)){ | ||||
| 
 | ||||
|             // Storage::url
 | ||||
|             foreach($this->purchase_order->documents as $document){ | ||||
|                 $this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]); | ||||
|             } | ||||
| 
 | ||||
|             foreach($this->purchase_order->company->documents as $document){ | ||||
|                 $this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]); | ||||
|             } | ||||
| 
 | ||||
|             // $line_items = $this->purchase_order->line_items;
 | ||||
|              | ||||
| 
 | ||||
|             // foreach($line_items as $item)
 | ||||
|             // {
 | ||||
| 
 | ||||
|             //     $expense_ids = [];
 | ||||
| 
 | ||||
|             //     if(property_exists($item, 'expense_id'))
 | ||||
|             //     {
 | ||||
|             //         $expense_ids[] = $item->expense_id;
 | ||||
|             //     }
 | ||||
| 
 | ||||
|             //     if(count($expense_ids) > 0){
 | ||||
| 
 | ||||
|             //         $expenses = Expense::whereIn('id', $this->transformKeys($expense_ids))
 | ||||
|             //                            ->where('invoice_documents', 1)
 | ||||
|             //                            ->cursor()
 | ||||
|             //                            ->each(function ($expense){
 | ||||
| 
 | ||||
|             //                                 foreach($expense->documents as $document)
 | ||||
|             //                                 {
 | ||||
|             //                                     $this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]);
 | ||||
|             //                                 }
 | ||||
| 
 | ||||
|             //                            });
 | ||||
|             //     }
 | ||||
| 
 | ||||
|             //     $task_ids = [];
 | ||||
| 
 | ||||
|             //     if(property_exists($item, 'task_id'))
 | ||||
|             //     {
 | ||||
|             //         $task_ids[] = $item->task_id;
 | ||||
|             //     }
 | ||||
| 
 | ||||
|             //     if(count($task_ids) > 0 && $this->purchase_order->company->purchase_order_task_documents){
 | ||||
|                      | ||||
|             //         $tasks = Task::whereIn('id', $this->transformKeys($task_ids))
 | ||||
|             //                            ->cursor()
 | ||||
|             //                            ->each(function ($task){
 | ||||
| 
 | ||||
|             //                                 foreach($task->documents as $document)
 | ||||
|             //                                 {
 | ||||
|             //                                     $this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]);
 | ||||
|             //                                 }
 | ||||
| 
 | ||||
|             //                            });
 | ||||
|             //     }
 | ||||
| 
 | ||||
|             // }
 | ||||
| 
 | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										132
									
								
								app/Mail/VendorTemplateEmail.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								app/Mail/VendorTemplateEmail.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,132 @@ | ||||
| <?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\Mail; | ||||
| 
 | ||||
| use App\Jobs\Invoice\CreateUbl; | ||||
| use App\Models\Account; | ||||
| use App\Models\Client; | ||||
| use App\Models\User; | ||||
| use App\Models\VendorContact; | ||||
| use App\Services\PdfMaker\Designs\Utilities\DesignHelpers; | ||||
| use App\Utils\HtmlEngine; | ||||
| use App\Utils\Ninja; | ||||
| use App\Utils\TemplateEngine; | ||||
| use App\Utils\VendorHtmlEngine; | ||||
| use Illuminate\Bus\Queueable; | ||||
| use Illuminate\Mail\Mailable; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| class VendorTemplateEmail extends Mailable | ||||
| { | ||||
| 
 | ||||
|     private $build_email; | ||||
| 
 | ||||
|     private $vendor; | ||||
| 
 | ||||
|     private $contact; | ||||
| 
 | ||||
|     private $company; | ||||
| 
 | ||||
|     private $invitation; | ||||
| 
 | ||||
|     public function __construct($build_email, VendorContact $contact, $invitation = null) | ||||
|     { | ||||
|         $this->build_email = $build_email; | ||||
| 
 | ||||
|         $this->contact = $contact; | ||||
| 
 | ||||
|         $this->vendor = $contact->vendor; | ||||
| 
 | ||||
|         $this->company = $contact->company; | ||||
| 
 | ||||
|         $this->invitation = $invitation; | ||||
|     } | ||||
| 
 | ||||
|     public function build() | ||||
|     { | ||||
|          | ||||
|         $template_name = 'email.template.'.$this->build_email->getTemplate(); | ||||
| 
 | ||||
|         if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') { | ||||
|             $template_name = 'email.template.client'; | ||||
|         } | ||||
| 
 | ||||
|         if($this->build_email->getTemplate() == 'custom') { | ||||
|             $this->build_email->setBody(str_replace('$body', $this->build_email->getBody(), $this->client->getSetting('email_style_custom'))); | ||||
|         } | ||||
| 
 | ||||
|         $settings = $this->company->settings; | ||||
| 
 | ||||
|         if ($this->build_email->getTemplate() !== 'custom') { | ||||
|             $this->build_email->setBody( | ||||
|                 DesignHelpers::parseMarkdownToHtml($this->build_email->getBody()) | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         if($this->invitation) | ||||
|         { | ||||
|             $html_variables = (new VendorHtmlEngine($this->invitation))->makeValues(); | ||||
|             $signature = str_replace(array_keys($html_variables), array_values($html_variables), $settings->email_signature); | ||||
|         } | ||||
|         else | ||||
|             $signature = $settings->email_signature; | ||||
| 
 | ||||
|         if(property_exists($settings, 'email_from_name') && strlen($settings->email_from_name) > 1) | ||||
|             $email_from_name = $settings->email_from_name; | ||||
|         else | ||||
|             $email_from_name = $this->company->present()->name(); | ||||
| 
 | ||||
|         $this->from(config('mail.from.address'), $email_from_name); | ||||
| 
 | ||||
|         if (strlen($settings->bcc_email) > 1) | ||||
|             $this->bcc(explode(",",str_replace(" ", "", $settings->bcc_email)));//remove whitespace if any has been inserted.
 | ||||
| 
 | ||||
|         $this->subject($this->build_email->getSubject()) | ||||
|             ->text('email.template.text', [ | ||||
|                 'text_body' => $this->build_email->getTextBody(), | ||||
|                 'whitelabel' => $this->vendor->user->account->isPaid() ? true : false, | ||||
|                 'settings' => $settings, | ||||
|             ]) | ||||
|             ->view($template_name, [ | ||||
|                 'greeting' => ctrans('texts.email_salutation', ['name' => $this->contact->present()->name()]), | ||||
|                 'body' => $this->build_email->getBody(), | ||||
|                 'footer' => $this->build_email->getFooter(), | ||||
|                 'view_link' => $this->build_email->getViewLink(), | ||||
|                 'view_text' => $this->build_email->getViewText(), | ||||
|                 'title' => '', | ||||
|                 'signature' => $signature, | ||||
|                 'settings' => $settings, | ||||
|                 'company' => $this->company, | ||||
|                 'whitelabel' => $this->vendor->user->account->isPaid() ? true : false, | ||||
|                 'logo' => $this->company->present()->logo($settings), | ||||
|             ]) | ||||
|             ->withSwiftMessage(function ($message) { | ||||
|                 $message->getHeaders()->addTextHeader('Tag', $this->company->company_key); | ||||
|                 $message->invitation = $this->invitation; | ||||
|             }); | ||||
| 
 | ||||
|             /*In the hosted platform we need to slow things down a little for Storage to catch up.*/ | ||||
|             if(Ninja::isHosted()) | ||||
|                 sleep(1); | ||||
| 
 | ||||
|             foreach ($this->build_email->getAttachments() as $file) { | ||||
| 
 | ||||
|                 if(is_string($file)) | ||||
|                     $this->attach($file); | ||||
|                 elseif(is_array($file)) | ||||
|                     $this->attach($file['path'], ['as' => $file['name'], 'mime' => $file['mime']]); | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| } | ||||
| @ -11,6 +11,7 @@ | ||||
| 
 | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use App\Exceptions\ModelNotFoundException; | ||||
| use App\Jobs\Mail\NinjaMailerJob; | ||||
| use App\Jobs\Mail\NinjaMailerObject; | ||||
| use App\Mail\Ninja\EmailQuotaExceeded; | ||||
| @ -57,6 +58,7 @@ class Account extends BaseModel | ||||
|         'utm_content', | ||||
|         'user_agent', | ||||
|         'platform', | ||||
|         'set_react_as_default_ap', | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
| @ -74,7 +76,8 @@ class Account extends BaseModel | ||||
|         'updated_at' => 'timestamp', | ||||
|         'created_at' => 'timestamp', | ||||
|         'deleted_at' => 'timestamp', | ||||
|         'onboarding' => 'object' | ||||
|         'onboarding' => 'object', | ||||
|         'set_react_as_default_ap' => 'bool' | ||||
|     ]; | ||||
| 
 | ||||
|     const PLAN_FREE = 'free'; | ||||
| @ -87,6 +90,7 @@ class Account extends BaseModel | ||||
|     const FEATURE_TASKS = 'tasks'; | ||||
|     const FEATURE_EXPENSES = 'expenses'; | ||||
|     const FEATURE_QUOTES = 'quotes'; | ||||
|     const FEATURE_PURCHASE_ORDERS = 'purchase_orders'; | ||||
|     const FEATURE_CUSTOMIZE_INVOICE_DESIGN = 'custom_designs'; | ||||
|     const FEATURE_DIFFERENT_DESIGNS = 'different_designs'; | ||||
|     const FEATURE_EMAIL_TEMPLATES_REMINDERS = 'template_reminders'; | ||||
| @ -162,6 +166,7 @@ class Account extends BaseModel | ||||
|             case self::FEATURE_TASKS: | ||||
|             case self::FEATURE_EXPENSES: | ||||
|             case self::FEATURE_QUOTES: | ||||
|             case self::FEATURE_PURCHASE_ORDERS: | ||||
|                 return true; | ||||
| 
 | ||||
|             case self::FEATURE_CUSTOMIZE_INVOICE_DESIGN: | ||||
| @ -468,4 +473,14 @@ class Account extends BaseModel | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function resolveRouteBinding($value, $field = null) | ||||
|     { | ||||
|         if (is_numeric($value)) { | ||||
|             throw new ModelNotFoundException("Record with value {$value} not found"); | ||||
|         } | ||||
| 
 | ||||
|         return $this | ||||
|             ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -116,7 +116,8 @@ class Activity extends StaticModel | ||||
|     const RESTORE_PURCHASE_ORDER = 134; | ||||
|     const EMAIL_PURCHASE_ORDER = 135;  | ||||
|     const VIEW_PURCHASE_ORDER = 136; | ||||
| 
 | ||||
|     const ACCEPT_PURCHASE_ORDER = 137; | ||||
|      | ||||
|     protected $casts = [ | ||||
|         'is_system' => 'boolean', | ||||
|         'updated_at' => 'timestamp', | ||||
|  | ||||
| @ -486,7 +486,7 @@ class Client extends BaseModel implements HasLocalePreference | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         if($this->currency()->code == 'EUR' && in_array(GatewayType::BANK_TRANSFER, array_column($pms, 'gateway_type_id'))){ | ||||
|         if($this->currency()->code == 'EUR' && (in_array(GatewayType::BANK_TRANSFER, array_column($pms, 'gateway_type_id')) || in_array(GatewayType::SEPA, array_column($pms, 'gateway_type_id'))) ){ | ||||
|          | ||||
|             foreach($pms as $pm){ | ||||
|                  | ||||
| @ -501,18 +501,6 @@ class Client extends BaseModel implements HasLocalePreference | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         // if ($this->currency()->code == 'EUR' && in_array(GatewayType::SEPA, array_column($pms, 'gateway_type_id'))) {
 | ||||
|         //     foreach ($pms as $pm) {
 | ||||
|         //         if ($pm['gateway_type_id'] == GatewayType::SEPA) {
 | ||||
|         //             $cg = CompanyGateway::find($pm['company_gateway_id']);
 | ||||
| 
 | ||||
|         //             if ($cg && $cg->fees_and_limits->{GatewayType::SEPA}->is_enabled) {
 | ||||
|         //                 return $cg;
 | ||||
|         //             }
 | ||||
|         //         }
 | ||||
|         //     }
 | ||||
|         // }
 | ||||
| 
 | ||||
|         if ($this->country && $this->country->iso_3166_3 == 'GBR' && in_array(GatewayType::DIRECT_DEBIT, array_column($pms, 'gateway_type_id'))) { | ||||
|             foreach ($pms as $pm) { | ||||
|                 if ($pm['gateway_type_id'] == GatewayType::DIRECT_DEBIT) { | ||||
|  | ||||
| @ -14,6 +14,7 @@ namespace App\Models; | ||||
| use App\DataMapper\CompanySettings; | ||||
| use App\Models\Language; | ||||
| use App\Models\Presenters\CompanyPresenter; | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Models\User; | ||||
| use App\Services\Notification\NotificationService; | ||||
| use App\Utils\Ninja; | ||||
| @ -192,6 +193,11 @@ class Company extends BaseModel | ||||
|         return $this->hasMany(Subscription::class)->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     public function purchase_orders() | ||||
|     { | ||||
|         return $this->hasMany(PurchaseOrder::class)->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     public function task_statuses() | ||||
|     { | ||||
|         return $this->hasMany(TaskStatus::class)->withTrashed(); | ||||
|  | ||||
| @ -346,7 +346,7 @@ class Invoice extends BaseModel | ||||
|                 return '<h5><span class="badge badge-danger">'.ctrans('texts.overdue').'</span></h5>'; | ||||
|                 break; | ||||
|             case self::STATUS_UNPAID: | ||||
|                 return '<h5><span class="badge badge-warning">'.ctrans('texts.unpaid').'</span></h5>'; | ||||
|                 return '<h5><span class="badge badge-warning text-white">'.ctrans('texts.unpaid').'</span></h5>'; | ||||
|                 break; | ||||
|             case self::STATUS_REVERSED: | ||||
|                 return '<h5><span class="badge badge-info">'.ctrans('texts.reversed').'</span></h5>'; | ||||
|  | ||||
| @ -212,7 +212,7 @@ class Payment extends BaseModel | ||||
|                 return '<h6><span class="badge badge-secondary">'.ctrans('texts.payment_status_1').'</span></h6>'; | ||||
|                 break; | ||||
|             case self::STATUS_CANCELLED: | ||||
|                 return '<h6><span class="badge badge-warning">'.ctrans('texts.payment_status_2').'</span></h6>'; | ||||
|                 return '<h6><span class="badge badge-warning text-white">'.ctrans('texts.payment_status_2').'</span></h6>'; | ||||
|                 break; | ||||
|             case self::STATUS_FAILED: | ||||
|                 return '<h6><span class="badge badge-danger">'.ctrans('texts.payment_status_3').'</span></h6>'; | ||||
|  | ||||
| @ -18,6 +18,7 @@ use App\Jobs\Entity\CreateEntityPdf; | ||||
| use App\Jobs\Vendor\CreatePurchaseOrderPdf; | ||||
| use App\Services\PurchaseOrder\PurchaseOrderService; | ||||
| use App\Utils\Ninja; | ||||
| use App\Utils\Traits\MakesDates; | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| use Illuminate\Support\Carbon; | ||||
| use Illuminate\Support\Facades\Storage; | ||||
| @ -26,6 +27,7 @@ class PurchaseOrder extends BaseModel | ||||
| { | ||||
|     use Filterable; | ||||
|     use SoftDeletes; | ||||
|     use MakesDates; | ||||
| 
 | ||||
|     protected $fillable = [ | ||||
|         'number', | ||||
| @ -99,8 +101,51 @@ class PurchaseOrder extends BaseModel | ||||
| 
 | ||||
|     const STATUS_DRAFT = 1; | ||||
|     const STATUS_SENT = 2; | ||||
|     const STATUS_PARTIAL = 3; | ||||
|     const STATUS_APPLIED = 4; | ||||
|     const STATUS_ACCEPTED = 3; | ||||
|     const STATUS_CANCELLED = 4; | ||||
| 
 | ||||
|     public static function stringStatus(int $status) | ||||
|     { | ||||
|         switch ($status) { | ||||
|             case self::STATUS_DRAFT: | ||||
|                 return ctrans('texts.draft'); | ||||
|                 break; | ||||
|             case self::STATUS_SENT: | ||||
|                 return ctrans('texts.sent'); | ||||
|                 break; | ||||
|             case self::STATUS_ACCEPTED: | ||||
|                 return ctrans('texts.accepted'); | ||||
|                 break; | ||||
|             case self::STATUS_CANCELLED: | ||||
|                 return ctrans('texts.cancelled'); | ||||
|                 break; | ||||
|                 // code...
 | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public static function badgeForStatus(int $status) | ||||
|     { | ||||
|         switch ($status) { | ||||
|             case self::STATUS_DRAFT: | ||||
|                 return '<h5><span class="badge badge-light">'.ctrans('texts.draft').'</span></h5>'; | ||||
|                 break; | ||||
|             case self::STATUS_SENT: | ||||
|                 return '<h5><span class="badge badge-primary">'.ctrans('texts.sent').'</span></h5>'; | ||||
|                 break; | ||||
|             case self::STATUS_ACCEPTED: | ||||
|                 return '<h5><span class="badge badge-primary">'.ctrans('texts.accepted').'</span></h5>'; | ||||
|                 break; | ||||
|             case self::STATUS_CANCELLED: | ||||
|                 return '<h5><span class="badge badge-secondary">'.ctrans('texts.cancelled').'</span></h5>'; | ||||
|                 break; | ||||
|             default: | ||||
|                 // code...
 | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public function assigned_user() | ||||
|     { | ||||
|  | ||||
| @ -1,4 +1,13 @@ | ||||
| <?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\Models; | ||||
| @ -9,6 +18,7 @@ use App\Utils\Traits\Inviteable; | ||||
| use App\Utils\Traits\MakesDates; | ||||
| use Carbon\Carbon; | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| use Illuminate\Support\Str; | ||||
| 
 | ||||
| class PurchaseOrderInvitation extends BaseModel | ||||
| { | ||||
| @ -104,4 +114,37 @@ class PurchaseOrderInvitation extends BaseModel | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function getLink() :string | ||||
|     { | ||||
|         $entity_type = Str::snake(class_basename($this->entityType())); | ||||
| 
 | ||||
|         if(Ninja::isHosted()){ | ||||
|             $domain = $this->company->domain(); | ||||
|         } | ||||
|         else | ||||
|             $domain = config('ninja.app_url'); | ||||
| 
 | ||||
|         switch ($this->company->portal_mode) { | ||||
|             case 'subdomain': | ||||
|                 return $domain.'/vendor/'.$entity_type.'/'.$this->key; | ||||
|                 break; | ||||
|             case 'iframe': | ||||
|                 return $domain.'/vendor/'.$entity_type.'/'.$this->key; | ||||
|                 break; | ||||
|             case 'domain': | ||||
|                 return $domain.'/vendor/'.$entity_type.'/'.$this->key; | ||||
|                 break; | ||||
| 
 | ||||
|             default: | ||||
|                 return ''; | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function getAdminLink() :string | ||||
|     { | ||||
|         return $this->getLink().'?silent=true'; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -73,6 +73,24 @@ class VendorContact extends Authenticatable implements HasLocalePreference | ||||
|         'vendor_id', | ||||
|     ]; | ||||
| 
 | ||||
|     public function avatar() | ||||
|     { | ||||
|         if ($this->avatar) { | ||||
|             return $this->avatar; | ||||
|         } | ||||
| 
 | ||||
|         return asset('images/svg/user.svg'); | ||||
|     } | ||||
|      | ||||
|     public function setAvatarAttribute($value) | ||||
|     { | ||||
|         if (! filter_var($value, FILTER_VALIDATE_URL) && $value) { | ||||
|             $this->attributes['avatar'] = url('/').$value; | ||||
|         } else { | ||||
|             $this->attributes['avatar'] = $value; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function getEntityType() | ||||
|     { | ||||
|         return self::class; | ||||
| @ -110,7 +128,7 @@ class VendorContact extends Authenticatable implements HasLocalePreference | ||||
| 
 | ||||
|     public function sendPasswordResetNotification($token) | ||||
|     { | ||||
|         $this->notify(new ClientContactResetPassword($token)); | ||||
|         // $this->notify(new ClientContactResetPassword($token));
 | ||||
|     } | ||||
| 
 | ||||
|     public function preferredLocale() | ||||
| @ -118,12 +136,9 @@ class VendorContact extends Authenticatable implements HasLocalePreference | ||||
|         $languages = Cache::get('languages'); | ||||
| 
 | ||||
|         return $languages->filter(function ($item) { | ||||
|             return $item->id == $this->client->getSetting('language_id'); | ||||
|             return $item->id == $this->company->getSetting('language_id'); | ||||
|         })->first()->locale; | ||||
| 
 | ||||
|         //$lang = Language::find($this->client->getSetting('language_id'));
 | ||||
| 
 | ||||
|         //return $lang->locale;
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -407,12 +407,7 @@ class BaseDriver extends AbstractPaymentDriver | ||||
|             $this->unWindGatewayFees($this->payment_hash); | ||||
|         } | ||||
| 
 | ||||
|         if ($e instanceof CheckoutHttpException) { | ||||
|             $error = $e->getBody(); | ||||
|         } else if ($e instanceof Exception) { | ||||
|             $error = $e->getMessage(); | ||||
|         } else | ||||
|             $error = $e->getMessage(); | ||||
|         $error = $e->getMessage(); | ||||
| 
 | ||||
|         if(!$this->payment_hash) | ||||
|             throw new PaymentFailed($error, $e->getCode()); | ||||
|  | ||||
| @ -14,18 +14,24 @@ namespace App\PaymentDrivers\CheckoutCom; | ||||
| 
 | ||||
| use App\Exceptions\PaymentFailed; | ||||
| use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; | ||||
| use Illuminate\Http\Request; | ||||
| use App\Models\ClientGatewayToken; | ||||
| use App\Models\GatewayType; | ||||
| use App\Models\Payment; | ||||
| use App\PaymentDrivers\CheckoutComPaymentDriver; | ||||
| use App\PaymentDrivers\Common\MethodInterface; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use Checkout\CheckoutApiException; | ||||
| use Checkout\CheckoutArgumentException; | ||||
| use Checkout\CheckoutAuthorizationException; | ||||
| use Checkout\Library\Exceptions\CheckoutHttpException; | ||||
| use Checkout\Models\Payments\IdSource; | ||||
| use Checkout\Models\Payments\Payment; | ||||
| use Checkout\Models\Payments\TokenSource; | ||||
| use Checkout\Payments\Four\Request\Source\RequestTokenSource; | ||||
| use Checkout\Payments\Source\RequestTokenSource as SourceRequestTokenSource; | ||||
| use Illuminate\Contracts\View\Factory; | ||||
| use Illuminate\Http\Request; | ||||
| use Illuminate\View\View; | ||||
| use Checkout\Payments\PaymentRequest as PaymentsPaymentRequest; | ||||
| use Checkout\Payments\Four\Request\PaymentRequest; | ||||
| 
 | ||||
| class CreditCard implements MethodInterface | ||||
| { | ||||
| @ -57,6 +63,31 @@ class CreditCard implements MethodInterface | ||||
|         return render('gateways.checkout.credit_card.authorize', $data); | ||||
|     } | ||||
| 
 | ||||
|     public function bootRequest($token) | ||||
|     { | ||||
| 
 | ||||
|         if($this->checkout->is_four_api){ | ||||
| 
 | ||||
|             $token_source = new RequestTokenSource(); | ||||
|             $token_source->token = $token; | ||||
|             $request = new PaymentRequest(); | ||||
|             $request->source = $token_source; | ||||
| 
 | ||||
| 
 | ||||
|         } | ||||
|         else { | ||||
| 
 | ||||
|             $token_source = new SourceRequestTokenSource(); | ||||
|             $token_source->token = $token; | ||||
|             $request = new PaymentsPaymentRequest(); | ||||
|             $request->source = $token_source; | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         return $request; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Handle authorization for credit card. | ||||
|      * | ||||
| @ -67,41 +98,54 @@ class CreditCard implements MethodInterface | ||||
|     { | ||||
|         $gateway_response = \json_decode($request->gateway_response); | ||||
| 
 | ||||
|         $method = new TokenSource( | ||||
|             $gateway_response->token | ||||
|         ); | ||||
|         $customerRequest = $this->checkout->getCustomer(); | ||||
|         $request = $this->bootRequest($gateway_response->token); | ||||
|         $request->capture = false; | ||||
|         $request->reference = '$1 payment for authorization.'; | ||||
|         $request->amount = 100; | ||||
|         $request->currency = $this->checkout->client->getCurrencyCode(); | ||||
|         $request->customer = $customerRequest; | ||||
| 
 | ||||
|         $payment = new Payment($method, 'USD'); | ||||
|         $payment->amount = 100; // $1
 | ||||
|         $payment->reference = '$1 payment for authorization.'; | ||||
|         $payment->capture = false; | ||||
| 
 | ||||
|         try { | ||||
|             $response = $this->checkout->gateway->payments()->request($payment); | ||||
| 
 | ||||
|             if ($response->approved && $response->status === 'Authorized') { | ||||
|             $response = $this->checkout->gateway->getPaymentsClient()->requestPayment($request); | ||||
| 
 | ||||
|             if ($response['approved'] && $response['status'] === 'Authorized') { | ||||
|                 $payment_meta = new \stdClass; | ||||
|                 $payment_meta->exp_month = (string) $response->source['expiry_month']; | ||||
|                 $payment_meta->exp_year = (string) $response->source['expiry_year']; | ||||
|                 $payment_meta->brand = (string) $response->source['scheme']; | ||||
|                 $payment_meta->last4 = (string) $response->source['last4']; | ||||
|                 $payment_meta->exp_month = (string) $response['source']['expiry_month']; | ||||
|                 $payment_meta->exp_year = (string) $response['source']['expiry_year']; | ||||
|                 $payment_meta->brand = (string) $response['source']['scheme']; | ||||
|                 $payment_meta->last4 = (string) $response['source']['last4']; | ||||
|                 $payment_meta->type = (int) GatewayType::CREDIT_CARD; | ||||
| 
 | ||||
|                 $data = [ | ||||
|                     'payment_meta' => $payment_meta, | ||||
|                     'token' => $response->source['id'], | ||||
|                     'payment_method_id' => GatewayType::CREDIT_CARD, | ||||
|                     'token' => $response['source']['id'], | ||||
|                     'payment_method_id' => GatewayType::CREDIT_CARD,   | ||||
|                 ]; | ||||
| 
 | ||||
|                 $payment_method = $this->checkout->storeGatewayToken($data); | ||||
|                 $payment_method = $this->checkout->storeGatewayToken($data,['gateway_customer_reference' => $customerRequest['id']]); | ||||
| 
 | ||||
|                 return redirect()->route('client.payment_methods.show', $payment_method->hashed_id); | ||||
|             } | ||||
|         } catch (CheckoutHttpException $exception) { | ||||
|             throw new PaymentFailed( | ||||
|                 $exception->getMessage() | ||||
|             ); | ||||
| 
 | ||||
| 
 | ||||
|         } catch (CheckoutApiException $e) { | ||||
|             // API error
 | ||||
|             $request_id = $e->request_id; | ||||
|             $http_status_code = $e->http_status_code; | ||||
|             $error_details = $e->error_details; | ||||
| 
 | ||||
|             throw new PaymentFailed($e->getMessage()); | ||||
|         } catch (CheckoutArgumentException $e) { | ||||
|             // Bad arguments
 | ||||
|             throw new PaymentFailed($e->getMessage()); | ||||
|         } catch (CheckoutAuthorizationException $e) { | ||||
|             // Bad Invalid authorization
 | ||||
|             throw new PaymentFailed($e->getMessage()); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function paymentView($data) | ||||
| @ -145,89 +189,102 @@ class CreditCard implements MethodInterface | ||||
|     { | ||||
|         $cgt = ClientGatewayToken::query() | ||||
|             ->where('id', $this->decodePrimaryKey($request->input('token'))) | ||||
|             ->where('company_id', auth()->guard('contact')->user()->client->company->id) | ||||
|             ->where('company_id', auth()->guard('contact')->user()->client->company_id) | ||||
|             ->first(); | ||||
| 
 | ||||
|         if (!$cgt) { | ||||
|             throw new PaymentFailed(ctrans('texts.payment_token_not_found'), 401); | ||||
|         } | ||||
| 
 | ||||
|         $method = new IdSource($cgt->token); | ||||
|         $paymentRequest = $this->checkout->bootTokenRequest($cgt->token); | ||||
| 
 | ||||
|         return $this->completePayment($method, $request); | ||||
|         return $this->completePayment($paymentRequest, $request); | ||||
|     } | ||||
| 
 | ||||
|     private function attemptPaymentUsingCreditCard(PaymentResponseRequest $request) | ||||
|     { | ||||
|         $checkout_response = $this->checkout->payment_hash->data->server_response; | ||||
| 
 | ||||
|         $method = new TokenSource( | ||||
|             $checkout_response->token | ||||
|         ); | ||||
|         $paymentRequest = $this->bootRequest($checkout_response->token); | ||||
| 
 | ||||
|         return $this->completePayment($method, $request); | ||||
|         return $this->completePayment($paymentRequest, $request); | ||||
|     } | ||||
| 
 | ||||
|     private function completePayment($method, PaymentResponseRequest $request) | ||||
|     private function completePayment($paymentRequest, PaymentResponseRequest $request) | ||||
|     { | ||||
| 
 | ||||
|         $payment = new Payment($method, $this->checkout->payment_hash->data->currency); | ||||
|         $payment->amount = $this->checkout->payment_hash->data->value; | ||||
|         $payment->reference = $this->checkout->getDescription(); | ||||
|         $payment->customer = [ | ||||
|             'name' => $this->checkout->client->present()->name() , | ||||
|             'email' => $this->checkout->client->present()->email(), | ||||
|         ]; | ||||
|         $paymentRequest->amount = $this->checkout->payment_hash->data->value; | ||||
|         $paymentRequest->reference = $this->checkout->getDescription(); | ||||
|         $paymentRequest->customer = $this->checkout->getCustomer(); | ||||
|         $paymentRequest->metadata = ['udf1' => "Invoice Ninja"]; | ||||
|         $paymentRequest->currency = $this->checkout->client->getCurrencyCode(); | ||||
| 
 | ||||
|         $payment->metadata = [ | ||||
|             'udf1' => "Invoice Ninja", | ||||
|         ]; | ||||
| 
 | ||||
|         $this->checkout->payment_hash->data = array_merge((array)$this->checkout->payment_hash->data, ['checkout_payment_ref' => $payment]); | ||||
|         $this->checkout->payment_hash->data = array_merge((array)$this->checkout->payment_hash->data, ['checkout_payment_ref' => $paymentRequest]); | ||||
|         $this->checkout->payment_hash->save(); | ||||
| 
 | ||||
|         if ($this->checkout->client->currency()->code == 'EUR' || $this->checkout->company_gateway->getConfigField('threeds')) { | ||||
|             $payment->{'3ds'} = ['enabled' => true]; | ||||
| 
 | ||||
|             $payment->{'success_url'} = route('checkout.3ds_redirect', [ | ||||
|             $paymentRequest->{'3ds'} = ['enabled' => true]; | ||||
| 
 | ||||
|             $paymentRequest->{'success_url'} = route('checkout.3ds_redirect', [ | ||||
|                 'company_key' => $this->checkout->client->company->company_key, | ||||
|                 'company_gateway_id' => $this->checkout->company_gateway->hashed_id, | ||||
|                 'hash' => $this->checkout->payment_hash->hash, | ||||
|             ]); | ||||
| 
 | ||||
|             $payment->{'failure_url'} = route('checkout.3ds_redirect', [ | ||||
|             $paymentRequest->{'failure_url'} = route('checkout.3ds_redirect', [ | ||||
|                 'company_key' => $this->checkout->client->company->company_key, | ||||
|                 'company_gateway_id' => $this->checkout->company_gateway->hashed_id, | ||||
|                 'hash' => $this->checkout->payment_hash->hash, | ||||
|             ]); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             $response = $this->checkout->gateway->payments()->request($payment); | ||||
|             // $response = $this->checkout->gateway->payments()->request($payment);
 | ||||
| 
 | ||||
|             if ($response->status == 'Authorized') { | ||||
|             $response = $this->checkout->gateway->getPaymentsClient()->requestPayment($paymentRequest); | ||||
| 
 | ||||
|             if ($response['status'] == 'Authorized') { | ||||
|                 return $this->processSuccessfulPayment($response); | ||||
|             } | ||||
| 
 | ||||
|             if ($response->status == 'Pending') { | ||||
|             if ($response['status'] == 'Pending') { | ||||
|                 $this->checkout->confirmGatewayFee(); | ||||
| 
 | ||||
|                 return $this->processPendingPayment($response); | ||||
|             } | ||||
| 
 | ||||
|             if ($response->status == 'Declined') { | ||||
|             if ($response['status'] == 'Declined') { | ||||
| 
 | ||||
|                 $this->checkout->unWindGatewayFees($this->checkout->payment_hash); | ||||
| 
 | ||||
|                 // $this->checkout->sendFailureMail($response->response_summary);
 | ||||
|                  | ||||
|                 //@todo - this will double up the checkout . com failed mails
 | ||||
|                 // $this->checkout->clientPaymentFailureMailer($response->status);
 | ||||
|                  | ||||
|                 return $this->processUnsuccessfulPayment($response); | ||||
|             } | ||||
|         } catch (CheckoutHttpException $e) { | ||||
|         }  | ||||
|         catch (CheckoutApiException $e) { | ||||
|             // API error
 | ||||
|             $request_id = $e->request_id; | ||||
|             $http_status_code = $e->http_status_code; | ||||
|             $error_details = $e->error_details; | ||||
| 
 | ||||
|          | ||||
|             $this->checkout->unWindGatewayFees($this->checkout->payment_hash); | ||||
|             return $this->checkout->processInternallyFailedPayment($this->checkout, $e); | ||||
| 
 | ||||
|         } catch (CheckoutArgumentException $e) { | ||||
|             // Bad arguments
 | ||||
| 
 | ||||
|             $this->checkout->unWindGatewayFees($this->checkout->payment_hash); | ||||
|             return $this->checkout->processInternallyFailedPayment($this->checkout, $e); | ||||
| 
 | ||||
|         } catch (CheckoutAuthorizationException $e) { | ||||
|             // Bad Invalid authorization
 | ||||
| 
 | ||||
|             $this->checkout->unWindGatewayFees($this->checkout->payment_hash); | ||||
|             return $this->checkout->processInternallyFailedPayment($this->checkout, $e); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -54,17 +54,18 @@ trait Utilities | ||||
|         return round($amount * 100); | ||||
|     } | ||||
| 
 | ||||
|     private function processSuccessfulPayment(Payment $_payment) | ||||
|     private function processSuccessfulPayment($_payment) | ||||
|     { | ||||
| 
 | ||||
|         if ($this->getParent()->payment_hash->data->store_card) { | ||||
|             $this->storeLocalPaymentMethod($_payment); | ||||
|         } | ||||
| 
 | ||||
|         $data = [ | ||||
|             'payment_method' => $_payment->source['id'], | ||||
|             'payment_method' => $_payment['source']['id'], | ||||
|             'payment_type' => 12, | ||||
|             'amount' => $this->getParent()->payment_hash->data->raw_value, | ||||
|             'transaction_reference' => $_payment->id, | ||||
|             'transaction_reference' => $_payment['id'], | ||||
|             'gateway_type_id' => GatewayType::CREDIT_CARD, | ||||
|         ]; | ||||
| 
 | ||||
| @ -82,15 +83,15 @@ trait Utilities | ||||
|         return redirect()->route('client.payments.show', ['payment' => $this->getParent()->encodePrimaryKey($payment->id)]); | ||||
|     } | ||||
| 
 | ||||
|     public function processUnsuccessfulPayment(Payment $_payment, $throw_exception = true) | ||||
|     public function processUnsuccessfulPayment($_payment, $throw_exception = true) | ||||
|     { | ||||
| 
 | ||||
|         $error_message = ''; | ||||
| 
 | ||||
|         if(property_exists($_payment, 'server_response')) | ||||
|             $error_message = $_payment->response_summary; | ||||
|         elseif(property_exists($_payment, 'status')) | ||||
|             $error_message = $_payment->status; | ||||
|         if(array_key_exists('response_summary',$_payment)) | ||||
|             $error_message = $_payment['response_summary']; | ||||
|         elseif(array_key_exists('status',$_payment)) | ||||
|             $error_message = $_payment['status']; | ||||
| 
 | ||||
|         $this->getParent()->sendFailureMail($error_message); | ||||
|                  | ||||
| @ -110,36 +111,37 @@ trait Utilities | ||||
| 
 | ||||
|         if ($throw_exception) { | ||||
| 
 | ||||
|             throw new PaymentFailed($_payment->status . " " . $error_message, $_payment->http_code); | ||||
|             throw new PaymentFailed($_payment['status'] . " " . $error_message, 500); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function processPendingPayment(Payment $_payment) | ||||
|     private function processPendingPayment($_payment) | ||||
|     { | ||||
| 
 | ||||
|         try { | ||||
|             return redirect($_payment->_links['redirect']['href']); | ||||
|             return redirect($_payment['_links']['redirect']['href']); | ||||
|         } catch (Exception $e) { | ||||
|             return $this->processInternallyFailedPayment($this->getParent(), $e); | ||||
|             return $this->getParent()->processInternallyFailedPayment($this->getParent(), $e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function storeLocalPaymentMethod(Payment $response) | ||||
|     private function storeLocalPaymentMethod($response) | ||||
|     { | ||||
|         try { | ||||
|             $payment_meta = new stdClass; | ||||
|             $payment_meta->exp_month = (string) $response->source['expiry_month']; | ||||
|             $payment_meta->exp_year = (string) $response->source['expiry_year']; | ||||
|             $payment_meta->brand = (string) $response->source['scheme']; | ||||
|             $payment_meta->last4 = (string) $response->source['last4']; | ||||
|             $payment_meta->exp_month = (string) $response['source']['expiry_month']; | ||||
|             $payment_meta->exp_year = (string) $response['source']['expiry_year']; | ||||
|             $payment_meta->brand = (string) $response['source']['scheme']; | ||||
|             $payment_meta->last4 = (string) $response['source']['last4']; | ||||
|             $payment_meta->type = (int) GatewayType::CREDIT_CARD; | ||||
| 
 | ||||
|             $data = [ | ||||
|                 'payment_meta' => $payment_meta, | ||||
|                 'token' => $response->source['id'], | ||||
|                 'token' => $response['source']['id'], | ||||
|                 'payment_method_id' => $this->getParent()->payment_hash->data->payment_method_id, | ||||
|             ]; | ||||
| 
 | ||||
|             return $this->getParent()->storePaymentMethod($data); | ||||
|             return $this->getParent()->storePaymentMethod($data, ['gateway_customer_reference' => $response['customer']['id']]); | ||||
|         } catch (Exception $e) { | ||||
|             session()->flash('message', ctrans('texts.payment_method_saving_failed')); | ||||
|         } | ||||
|  | ||||
| @ -28,10 +28,22 @@ use App\PaymentDrivers\CheckoutCom\CreditCard; | ||||
| use App\PaymentDrivers\CheckoutCom\Utilities; | ||||
| use App\Utils\Traits\SystemLogTrait; | ||||
| use Checkout\CheckoutApi; | ||||
| use Checkout\CheckoutApiException; | ||||
| use Checkout\CheckoutArgumentException; | ||||
| use Checkout\CheckoutAuthorizationException; | ||||
| use Checkout\CheckoutDefaultSdk; | ||||
| use Checkout\CheckoutFourSdk; | ||||
| use Checkout\Environment; | ||||
| use Checkout\Library\Exceptions\CheckoutHttpException; | ||||
| use Checkout\Models\Payments\IdSource; | ||||
| use Checkout\Models\Payments\Refund; | ||||
| use Exception; | ||||
| use Checkout\Payments\Four\Request\PaymentRequest; | ||||
| use Checkout\Payments\Four\Request\Source\RequestIdSource as SourceRequestIdSource; | ||||
| use Checkout\Payments\PaymentRequest as PaymentsPaymentRequest; | ||||
| use Checkout\Payments\Source\RequestIdSource; | ||||
| use Checkout\Common\CustomerRequest; | ||||
| use Checkout\Payments\RefundRequest; | ||||
| 
 | ||||
| class CheckoutComPaymentDriver extends BaseDriver | ||||
| { | ||||
| @ -52,6 +64,8 @@ class CheckoutComPaymentDriver extends BaseDriver | ||||
|     /* Authorise payment methods */ | ||||
|     public $can_authorise_credit_card = true; | ||||
| 
 | ||||
|     public $is_four_api = false; | ||||
| 
 | ||||
|     /** | ||||
|      * @var CheckoutApi; | ||||
|      */ | ||||
| @ -109,7 +123,22 @@ class CheckoutComPaymentDriver extends BaseDriver | ||||
|             'sandbox' => $this->company_gateway->getConfigField('testMode'), | ||||
|         ]; | ||||
| 
 | ||||
|         $this->gateway = new CheckoutApi($config['secret'], $config['sandbox'], $config['public']); | ||||
| 
 | ||||
|         if(strlen($config['secret']) <= 38){ | ||||
|             $this->is_four_api = true; | ||||
|             $builder = CheckoutFourSdk::staticKeys(); | ||||
|             $builder->setPublicKey($config['public']); // optional, only required for operations related with tokens
 | ||||
|             $builder->setSecretKey($config['secret']); | ||||
|             $builder->setEnvironment($config['sandbox'] ? Environment::sandbox(): Environment::production()); | ||||
|             $this->gateway = $builder->build(); | ||||
|         } | ||||
|         else    { | ||||
|             $builder = CheckoutDefaultSdk::staticKeys(); | ||||
|             $builder->setPublicKey($config['public']); // optional, only required for operations related with tokens
 | ||||
|             $builder->setSecretKey($config['secret']); | ||||
|             $builder->setEnvironment($config['sandbox'] ? Environment::sandbox(): Environment::production()); | ||||
|             $this->gateway = $builder->build(); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| @ -121,9 +150,6 @@ class CheckoutComPaymentDriver extends BaseDriver | ||||
|      */ | ||||
|     public function viewForType($gateway_type_id) | ||||
|     { | ||||
|         // At the moment Checkout.com payment
 | ||||
|         // driver only supports payments using credit card.
 | ||||
| 
 | ||||
|         return 'gateways.checkout.credit_card.pay'; | ||||
|     } | ||||
| 
 | ||||
| @ -211,22 +237,40 @@ class CheckoutComPaymentDriver extends BaseDriver | ||||
|     { | ||||
|         $this->init(); | ||||
| 
 | ||||
|         $checkout_payment = new Refund($payment->transaction_reference); | ||||
|         $request = new RefundRequest(); | ||||
|         $request->reference = "{$payment->transaction_reference} " . now(); | ||||
|         $request->amount = $this->convertToCheckoutAmount($amount, $this->client->getCurrencyCode()); | ||||
| 
 | ||||
|         try { | ||||
|             $refund = $this->gateway->payments()->refund($checkout_payment); | ||||
|             $checkout_payment = $this->gateway->payments()->details($refund->id); | ||||
| 
 | ||||
|             $response = ['refund_response' => $refund, 'checkout_payment_fetch' => $checkout_payment]; | ||||
|             // or, refundPayment("payment_id") for a full refund
 | ||||
|             $response = $this->gateway->getPaymentsClient()->refundPayment($payment->transaction_reference, $request); | ||||
| 
 | ||||
|             return [ | ||||
|                 'transaction_reference' => $refund->action_id, | ||||
|                 'transaction_reference' => $response['action_id'], | ||||
|                 'transaction_response' => json_encode($response), | ||||
|                 'success' => $checkout_payment->status == 'Refunded', | ||||
|                 'description' => $checkout_payment->status, | ||||
|                 'code' => $checkout_payment->http_code, | ||||
|                 'success' => true, | ||||
|                 'description' => $response['reference'], | ||||
|                 'code' => 202, | ||||
|             ]; | ||||
|         } catch (CheckoutHttpException $e) { | ||||
| 
 | ||||
|         } catch (CheckoutApiException $e) { | ||||
|             // API error
 | ||||
|             $request_id = $e->request_id; | ||||
|             $http_status_code = $e->http_status_code; | ||||
|             $error_details = $e->error_details; | ||||
|         } catch (CheckoutArgumentException $e) { | ||||
|             // Bad arguments
 | ||||
| 
 | ||||
|             return [ | ||||
|                 'transaction_reference' => null, | ||||
|                 'transaction_response' => json_encode($e->getMessage()), | ||||
|                 'success' => false, | ||||
|                 'description' => $e->getMessage(), | ||||
|                 'code' => $e->getCode(), | ||||
|             ]; | ||||
|         } catch (CheckoutAuthorizationException $e) { | ||||
|             // Bad Invalid authorization
 | ||||
| 
 | ||||
|             return [ | ||||
|                 'transaction_reference' => null, | ||||
|                 'transaction_response' => json_encode($e->getMessage()), | ||||
| @ -235,6 +279,49 @@ class CheckoutComPaymentDriver extends BaseDriver | ||||
|                 'code' => $e->getCode(), | ||||
|             ]; | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function getCustomer() | ||||
|     { | ||||
|         try{ | ||||
|          | ||||
|         $response = $this->gateway->getCustomersClient()->get($this->client->present()->email()); | ||||
|              | ||||
|             return $response; | ||||
|         } | ||||
|         catch(\Exception $e){ | ||||
| 
 | ||||
|             $request = new CustomerRequest(); | ||||
|             $request->email = $this->client->present()->email(); | ||||
|             $request->name = $this->client->present()->name(); | ||||
|             return $request; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function bootTokenRequest($token) | ||||
|     { | ||||
| 
 | ||||
|         if($this->is_four_api){ | ||||
| 
 | ||||
|             $token_source = new SourceRequestIdSource(); | ||||
|             $token_source->id = $token; | ||||
|             $request = new PaymentRequest(); | ||||
|             $request->source = $token_source; | ||||
| 
 | ||||
| 
 | ||||
|         } | ||||
|         else { | ||||
| 
 | ||||
|             $token_source = new RequestIdSource(); | ||||
|             $token_source->id = $token; | ||||
|             $request = new PaymentsPaymentRequest(); | ||||
|             $request->source = $token_source; | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         return $request; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) | ||||
| @ -244,27 +331,29 @@ class CheckoutComPaymentDriver extends BaseDriver | ||||
| 
 | ||||
|         $this->init(); | ||||
| 
 | ||||
|         $method = new IdSource($cgt->token); | ||||
| 
 | ||||
|         $payment = new \Checkout\Models\Payments\Payment($method, $this->client->getCurrencyCode()); | ||||
|         $payment->amount = $this->convertToCheckoutAmount($amount, $this->client->getCurrencyCode()); | ||||
|         $payment->reference = $invoice->number . '-' . now(); | ||||
|         $paymentRequest = $this->bootTokenRequest($cgt->token); | ||||
|         $paymentRequest->amount = $this->convertToCheckoutAmount($amount, $this->client->getCurrencyCode()); | ||||
|         $paymentRequest->reference = '#' . $invoice->number . ' - ' . now(); | ||||
|         $paymentRequest->customer = $this->getCustomer(); | ||||
|         $paymentRequest->metadata = ['udf1' => "Invoice Ninja"]; | ||||
|         $paymentRequest->currency = $this->client->getCurrencyCode(); | ||||
| 
 | ||||
|         $request = new PaymentResponseRequest(); | ||||
|         $request->setMethod('POST'); | ||||
|         $request->request->add(['payment_hash' => $payment_hash->hash]); | ||||
| 
 | ||||
|         try { | ||||
|             $response = $this->gateway->payments()->request($payment); | ||||
|             // $response = $this->gateway->payments()->request($payment);
 | ||||
|             $response = $this->gateway->getPaymentsClient()->requestPayment($paymentRequest); | ||||
| 
 | ||||
|             if ($response->status == 'Authorized') { | ||||
|             if ($response['status'] == 'Authorized') { | ||||
|                 $this->confirmGatewayFee($request); | ||||
| 
 | ||||
|                 $data = [ | ||||
|                     'payment_method' => $response->source['id'], | ||||
|                     'payment_type' => PaymentType::parseCardType(strtolower($response->source['scheme'])), | ||||
|                     'payment_method' => $response['source']['id'], | ||||
|                     'payment_type' => PaymentType::parseCardType(strtolower($response['source']['scheme'])), | ||||
|                     'amount' => $amount, | ||||
|                     'transaction_reference' => $response->id, | ||||
|                     'transaction_reference' => $response['id'], | ||||
|                 ]; | ||||
| 
 | ||||
|                 $payment = $this->createPayment($data, Payment::STATUS_COMPLETED); | ||||
| @ -280,10 +369,10 @@ class CheckoutComPaymentDriver extends BaseDriver | ||||
|                 return $payment; | ||||
|             } | ||||
| 
 | ||||
|             if ($response->status == 'Declined') { | ||||
|             if ($response['status'] == 'Declined') { | ||||
|                 $this->unWindGatewayFees($payment_hash); | ||||
| 
 | ||||
|                 $this->sendFailureMail($response->status . " " . $response->response_summary); | ||||
|                 $this->sendFailureMail($response['status'] . " " . $response['response_summary']); | ||||
| 
 | ||||
|                 $message = [ | ||||
|                     'server_response' => $response, | ||||
| @ -300,11 +389,9 @@ class CheckoutComPaymentDriver extends BaseDriver | ||||
| 
 | ||||
|                 return false; | ||||
|             } | ||||
|         } catch (Exception | CheckoutHttpException $e) { | ||||
|         } catch (Exception | CheckoutApiException $e) { | ||||
|             $this->unWindGatewayFees($payment_hash); | ||||
|             $message = $e instanceof CheckoutHttpException | ||||
|                 ? $e->getBody() | ||||
|                 : $e->getMessage(); | ||||
|             $message = $e->getMessage(); | ||||
| 
 | ||||
|             $data = [ | ||||
|                 'status' => '', | ||||
| @ -334,20 +421,21 @@ class CheckoutComPaymentDriver extends BaseDriver | ||||
| 
 | ||||
|     public function process3dsConfirmation(Checkout3dsRequest $request) | ||||
|     { | ||||
| 
 | ||||
|         $this->init(); | ||||
|         $this->setPaymentHash($request->getPaymentHash()); | ||||
| 
 | ||||
|         try { | ||||
|             $payment = $this->gateway->payments()->details( | ||||
|             $payment = $this->gateway->getPaymentsClient()->getPaymentDetails( | ||||
|                 $request->query('cko-session-id') | ||||
|             ); | ||||
| 
 | ||||
|             if ($payment->approved) { | ||||
|             if ($payment['approved']) { | ||||
|                 return $this->processSuccessfulPayment($payment); | ||||
|             } else { | ||||
|                 return $this->processUnsuccessfulPayment($payment); | ||||
|             } | ||||
|         } catch (CheckoutHttpException | Exception $e) { | ||||
|         } catch (CheckoutApiException | Exception $e) { | ||||
|             return $this->processInternallyFailedPayment($this, $e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -235,7 +235,7 @@ class GoCardlessPaymentDriver extends BaseDriver | ||||
|         nlog("GoCardless Event"); | ||||
|         nlog($request->all()); | ||||
| 
 | ||||
|         if(!is_array($request->events) || !is_object($request->events)){ | ||||
|         if(!$request->has("events")){ | ||||
| 
 | ||||
|             nlog("No GoCardless events to process in response?"); | ||||
|             return response()->json([], 200); | ||||
| @ -245,18 +245,19 @@ class GoCardlessPaymentDriver extends BaseDriver | ||||
|         sleep(1); | ||||
| 
 | ||||
|         foreach ($request->events as $event) { | ||||
|             if ($event['action'] === 'confirmed' || $event['action'] === 'paid_out' || $event['action'] === 'paid') { | ||||
|             if ($event['action'] === 'confirmed' || $event['action'] === 'paid_out') { | ||||
| 
 | ||||
|                 nlog("Searching for transaction reference"); | ||||
|                  | ||||
|                 $payment = Payment::query() | ||||
|                     ->where('transaction_reference', $event['links']['payment']) | ||||
|                     // ->where('company_id', $request->getCompany()->id)
 | ||||
|                     ->where('company_id', $request->getCompany()->id) | ||||
|                     ->first(); | ||||
| 
 | ||||
|                 if ($payment) { | ||||
|                     $payment->status_id = Payment::STATUS_COMPLETED; | ||||
|                     $payment->save(); | ||||
|                     nlog("GoCardless completed"); | ||||
|                 } | ||||
|                 else | ||||
|                     nlog("I was unable to find the payment for this reference"); | ||||
| @ -268,12 +269,13 @@ class GoCardlessPaymentDriver extends BaseDriver | ||||
| 
 | ||||
|                 $payment = Payment::query() | ||||
|                     ->where('transaction_reference', $event['links']['payment']) | ||||
|                     // ->where('company_id', $request->getCompany()->id)
 | ||||
|                     ->where('company_id', $request->getCompany()->id) | ||||
|                     ->first(); | ||||
| 
 | ||||
|                 if ($payment) { | ||||
|                     $payment->status_id = Payment::STATUS_FAILED; | ||||
|                     $payment->save(); | ||||
|                     nlog("GoCardless completed"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @ -60,11 +60,12 @@ use App\Events\Payment\PaymentWasRefunded; | ||||
| use App\Events\Payment\PaymentWasRestored; | ||||
| use App\Events\Payment\PaymentWasUpdated; | ||||
| use App\Events\Payment\PaymentWasVoided; | ||||
| use App\Events\PurchaseOrder\PurchaseOrderWasMarkedSent; | ||||
| use App\Events\PurchaseOrder\PurchaseOrderWasAccepted; | ||||
| use App\Events\PurchaseOrder\PurchaseOrderWasArchived; | ||||
| use App\Events\PurchaseOrder\PurchaseOrderWasCreated; | ||||
| use App\Events\PurchaseOrder\PurchaseOrderWasDeleted; | ||||
| use App\Events\PurchaseOrder\PurchaseOrderWasEmailed; | ||||
| use App\Events\PurchaseOrder\PurchaseOrderWasMarkedSent; | ||||
| use App\Events\PurchaseOrder\PurchaseOrderWasRestored; | ||||
| use App\Events\PurchaseOrder\PurchaseOrderWasUpdated; | ||||
| use App\Events\PurchaseOrder\PurchaseOrderWasViewed; | ||||
| @ -179,6 +180,8 @@ use App\Listeners\Payment\PaymentEmailedActivity; | ||||
| use App\Listeners\Payment\PaymentNotification; | ||||
| use App\Listeners\Payment\PaymentRestoredActivity; | ||||
| use App\Listeners\PurchaseOrder\CreatePurchaseOrderActivity; | ||||
| use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedActivity; | ||||
| use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedNotification; | ||||
| use App\Listeners\PurchaseOrder\PurchaseOrderArchivedActivity; | ||||
| use App\Listeners\PurchaseOrder\PurchaseOrderDeletedActivity; | ||||
| use App\Listeners\PurchaseOrder\PurchaseOrderEmailActivity; | ||||
| @ -264,9 +267,9 @@ class EventServiceProvider extends ServiceProvider | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $listen = [ | ||||
|         AccountCreated::class =>[ | ||||
|         AccountCreated::class => [ | ||||
|         ], | ||||
|         MessageSending::class =>[ | ||||
|         MessageSending::class => [ | ||||
|         ], | ||||
|         MessageSent::class => [ | ||||
|             MailSentListener::class, | ||||
| @ -312,35 +315,35 @@ class EventServiceProvider extends ServiceProvider | ||||
|         PaymentWasVoided::class => [ | ||||
|             PaymentVoidedActivity::class, | ||||
|         ], | ||||
|         PaymentWasRestored::class =>[ | ||||
|         PaymentWasRestored::class => [ | ||||
|             PaymentRestoredActivity::class, | ||||
|         ], | ||||
|         // Clients
 | ||||
|         ClientWasCreated::class =>[ | ||||
|         ClientWasCreated::class => [ | ||||
|             CreatedClientActivity::class, | ||||
|         ], | ||||
|         ClientWasArchived::class =>[ | ||||
|         ClientWasArchived::class => [ | ||||
|             ArchivedClientActivity::class, | ||||
|         ], | ||||
|         ClientWasUpdated::class =>[ | ||||
|         ClientWasUpdated::class => [ | ||||
|             ClientUpdatedActivity::class, | ||||
|         ], | ||||
|         ClientWasDeleted::class =>[ | ||||
|         ClientWasDeleted::class => [ | ||||
|             DeleteClientActivity::class, | ||||
|         ], | ||||
|         ClientWasRestored::class =>[ | ||||
|         ClientWasRestored::class => [ | ||||
|             RestoreClientActivity::class, | ||||
|         ], | ||||
|         // Documents
 | ||||
|         DocumentWasCreated::class =>[ | ||||
|         DocumentWasCreated::class => [ | ||||
|         ], | ||||
|         DocumentWasArchived::class =>[ | ||||
|         DocumentWasArchived::class => [ | ||||
|         ], | ||||
|         DocumentWasUpdated::class =>[ | ||||
|         DocumentWasUpdated::class => [ | ||||
|         ], | ||||
|         DocumentWasDeleted::class =>[ | ||||
|         DocumentWasDeleted::class => [ | ||||
|         ], | ||||
|         DocumentWasRestored::class =>[ | ||||
|         DocumentWasRestored::class => [ | ||||
|         ], | ||||
|         CreditWasCreated::class => [ | ||||
|             CreatedCreditActivity::class, | ||||
| @ -404,11 +407,11 @@ class EventServiceProvider extends ServiceProvider | ||||
|         InvoiceWasCreated::class => [ | ||||
|             CreateInvoiceActivity::class, | ||||
|             InvoiceCreatedNotification::class, | ||||
|         //    CreateInvoicePdf::class,
 | ||||
|             //    CreateInvoicePdf::class,
 | ||||
|         ], | ||||
|         InvoiceWasPaid::class => [ | ||||
|            InvoicePaidActivity::class, | ||||
|            CreateInvoicePdf::class, | ||||
|             InvoicePaidActivity::class, | ||||
|             CreateInvoicePdf::class, | ||||
|         ], | ||||
|         InvoiceWasViewed::class => [ | ||||
|             InvoiceViewedActivity::class, | ||||
| @ -471,6 +474,10 @@ class EventServiceProvider extends ServiceProvider | ||||
|         PurchaseOrderWasViewed::class => [ | ||||
|             PurchaseOrderViewedActivity::class, | ||||
|         ], | ||||
|         PurchaseOrderWasAccepted::class => [ | ||||
|             PurchaseOrderAcceptedActivity::class, | ||||
|             PurchaseOrderAcceptedNotification::class | ||||
|         ], | ||||
|         CompanyDocumentsDeleted::class => [ | ||||
|             DeleteCompanyDocuments::class, | ||||
|         ], | ||||
| @ -593,7 +600,12 @@ class EventServiceProvider extends ServiceProvider | ||||
|         ], | ||||
|         VendorWasUpdated::class => [ | ||||
|             VendorUpdatedActivity::class, | ||||
|         ] | ||||
|         ], | ||||
|         \SocialiteProviders\Manager\SocialiteWasCalled::class => [ | ||||
|             // ... Manager won't register drivers that are not added to this listener.
 | ||||
|             \SocialiteProviders\Apple\AppleExtendSocialite::class . '@handle', | ||||
|             \SocialiteProviders\Microsoft\MicrosoftExtendSocialite::class . '@handle', | ||||
|         ], | ||||
| 
 | ||||
|     ]; | ||||
| 
 | ||||
|  | ||||
| @ -50,6 +50,8 @@ class RouteServiceProvider extends ServiceProvider | ||||
| 
 | ||||
|         $this->mapContactApiRoutes(); | ||||
| 
 | ||||
|         $this->mapVendorsApiRoutes(); | ||||
| 
 | ||||
|         $this->mapClientApiRoutes(); | ||||
| 
 | ||||
|         $this->mapShopApiRoutes(); | ||||
| @ -121,4 +123,12 @@ class RouteServiceProvider extends ServiceProvider | ||||
|              ->namespace($this->namespace) | ||||
|              ->group(base_path('routes/shop.php')); | ||||
|     } | ||||
| 
 | ||||
|     protected function mapVendorsApiRoutes() | ||||
|     { | ||||
|         Route::prefix('') | ||||
|             ->middleware('client') | ||||
|             ->namespace($this->namespace) | ||||
|             ->group(base_path('routes/vendor.php')); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -49,8 +49,6 @@ class GenerateDeliveryNote | ||||
| 
 | ||||
|         $this->contact = $contact; | ||||
| 
 | ||||
|         // $this->disk = 'public';
 | ||||
| 
 | ||||
|         $this->disk = $disk ?? config('filesystems.default'); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -341,7 +341,7 @@ class InvoiceService | ||||
|             if(Storage::disk(config('filesystems.default'))->exists($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf')) | ||||
|                 Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf'); | ||||
|              | ||||
|             if(Ninja::isHosted() && Storage::disk(config('filesystems.default'))->exists($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf')) { | ||||
|             if(Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf')) { | ||||
|                 Storage::disk('public')->delete($this->invoice->client->invoice_filepath($invitation) . $this->invoice->numberFormatter().'.pdf'); | ||||
|             } | ||||
| 
 | ||||
|  | ||||
| @ -56,6 +56,10 @@ class Design extends BaseDesign | ||||
|     /** @var Payment[] */ | ||||
|     public $payments; | ||||
| 
 | ||||
|     public $settings_object; | ||||
| 
 | ||||
|     public $company; | ||||
| 
 | ||||
|     /** @var array */ | ||||
|     public $aging = []; | ||||
| 
 | ||||
| @ -80,6 +84,7 @@ class Design extends BaseDesign | ||||
|         Str::endsWith('.html', $design) ? $this->design = $design : $this->design = "{$design}.html"; | ||||
| 
 | ||||
|         $this->options = $options; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function html(): ?string | ||||
| @ -574,19 +579,19 @@ class Design extends BaseDesign | ||||
| 
 | ||||
|         foreach ($this->context['pdf_variables']["{$type}_columns"] as $column) { | ||||
|             if (array_key_exists($column, $aliases)) { | ||||
|                 $elements[] = ['element' => 'th', 'content' => $aliases[$column] . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($aliases[$column], 1) . '-th', 'hidden' => $this->client->getSetting('hide_empty_columns_on_pdf')]]; | ||||
|             } elseif ($column == '$product.discount' && !$this->client->company->enable_product_discount) { | ||||
|                 $elements[] = ['element' => 'th', 'content' => $aliases[$column] . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($aliases[$column], 1) . '-th', 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]]; | ||||
|             } elseif ($column == '$product.discount' && !$this->company->enable_product_discount) { | ||||
|                 $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'style' => 'display: none;']]; | ||||
|             } elseif ($column == '$product.quantity' && !$this->client->company->enable_product_quantity) { | ||||
|             } elseif ($column == '$product.quantity' && !$this->company->enable_product_quantity) { | ||||
|                 $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'style' => 'display: none;']]; | ||||
|             } elseif ($column == '$product.tax_rate1') { | ||||
|                 $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax1-th", 'hidden' => $this->client->getSetting('hide_empty_columns_on_pdf')]]; | ||||
|                 $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax1-th", 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]]; | ||||
|             } elseif ($column == '$product.tax_rate2') { | ||||
|                 $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax2-th", 'hidden' => $this->client->getSetting('hide_empty_columns_on_pdf')]]; | ||||
|                 $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax2-th", 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]]; | ||||
|             } elseif ($column == '$product.tax_rate3') { | ||||
|                 $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax3-th", 'hidden' => $this->client->getSetting('hide_empty_columns_on_pdf')]]; | ||||
|                 $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax3-th", 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]]; | ||||
|             } else { | ||||
|                 $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'hidden' => $this->client->getSetting('hide_empty_columns_on_pdf')]]; | ||||
|                 $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]]; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @ -677,9 +682,9 @@ class Design extends BaseDesign | ||||
| 
 | ||||
|                     if ($cell == '$task.rate') { | ||||
|                         $element['elements'][] = ['element' => 'td', 'content' => $row['$task.cost'], 'properties' => ['data-ref' => 'task_table-task.cost-td']]; | ||||
|                     } elseif ($cell == '$product.discount' && !$this->client->company->enable_product_discount) { | ||||
|                     } elseif ($cell == '$product.discount' && !$this->company->enable_product_discount) { | ||||
|                         $element['elements'][] = ['element' => 'td', 'content' => $row['$product.discount'], 'properties' => ['data-ref' => 'product_table-product.discount-td', 'style' => 'display: none;']]; | ||||
|                     } elseif ($cell == '$product.quantity' && !$this->client->company->enable_product_quantity) { | ||||
|                     } elseif ($cell == '$product.quantity' && !$this->company->enable_product_quantity) { | ||||
|                         $element['elements'][] = ['element' => 'td', 'content' => $row['$product.quantity'], 'properties' => ['data-ref' => 'product_table-product.quantity-td', 'style' => 'display: none;']]; | ||||
|                     } elseif ($cell == '$task.hours') { | ||||
|                         $element['elements'][] = ['element' => 'td', 'content' => $row['$task.quantity'], 'properties' => ['data-ref' => 'task_table-task.hours-td']]; | ||||
| @ -799,7 +804,7 @@ class Design extends BaseDesign | ||||
|             } elseif (Str::startsWith($variable, '$custom_surcharge')) { | ||||
|                 $_variable = ltrim($variable, '$'); // $custom_surcharge1 -> custom_surcharge1
 | ||||
| 
 | ||||
|                 $visible = $this->entity->{$_variable} != 0 || $this->entity->{$_variable} != '0'; | ||||
|                 $visible = (int)$this->entity->{$_variable} != 0 || $this->entity->{$_variable} != '0' || !$this->entity->{$_variable}; | ||||
| 
 | ||||
|                 $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ | ||||
|                     ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], | ||||
| @ -807,7 +812,7 @@ class Design extends BaseDesign | ||||
|                 ]]; | ||||
|             } elseif (Str::startsWith($variable, '$custom')) { | ||||
|                 $field = explode('_', $variable); | ||||
|                 $visible = is_object($this->client->company->custom_fields) && property_exists($this->client->company->custom_fields, $field[1]) && !empty($this->client->company->custom_fields->{$field[1]}); | ||||
|                 $visible = is_object($this->company->custom_fields) && property_exists($this->company->custom_fields, $field[1]) && !empty($this->company->custom_fields->{$field[1]}); | ||||
| 
 | ||||
|                 $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ | ||||
|                     ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], | ||||
|  | ||||
| @ -60,6 +60,10 @@ trait DesignHelpers | ||||
| 
 | ||||
|         $this->document(); | ||||
| 
 | ||||
|         $this->settings_object = $this->vendor ? $this->vendor->company : $this->client; | ||||
| 
 | ||||
|         $this->company = $this->vendor ? $this->vendor->company : $this->client->company; | ||||
|          | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
| @ -180,7 +184,7 @@ trait DesignHelpers | ||||
| 
 | ||||
|             $key = array_search(sprintf('%s%s.tax', '$', $type), $this->context['pdf_variables']["{$type}_columns"], true); | ||||
| 
 | ||||
|             if ($key) { | ||||
|             if ($key !== false) { | ||||
|                 array_splice($this->context['pdf_variables']["{$type}_columns"], $key, 1, $taxes); | ||||
|             } | ||||
|         } | ||||
| @ -338,7 +342,7 @@ document.addEventListener('DOMContentLoaded', function() { | ||||
| 
 | ||||
|         $key = array_search(sprintf('%s%s.description', '$', $type), $this->context['pdf_variables']["{$type}_columns"], true); | ||||
| 
 | ||||
|         if ($key) { | ||||
|         if ($key !== false) { | ||||
|             array_splice($this->context['pdf_variables']["{$type}_columns"], $key + 1, 0, $custom_columns); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -42,7 +42,7 @@ class CreateInvitations extends AbstractService | ||||
| 
 | ||||
|     public function run() | ||||
|     { | ||||
|         $contacts = $this->purchase_order->vendor->contacts()->where('send_email', true)->get(); | ||||
|         $contacts = $this->purchase_order->vendor->contacts()->get(); | ||||
| 
 | ||||
|         if($contacts->count() == 0){ | ||||
|             $this->createBlankContact(); | ||||
|  | ||||
| @ -31,7 +31,7 @@ class GetPurchaseOrderPdf extends AbstractService | ||||
|     { | ||||
| 
 | ||||
|         if (! $this->contact) { | ||||
|             $this->contact = $this->purchase_order->vendor->contacts()->where('send_email', true)->first(); | ||||
|             $this->contact = $this->purchase_order->vendor->contacts()->orderBy('send_email', 'DESC')->first(); | ||||
|         } | ||||
| 
 | ||||
|         $invitation = $this->purchase_order->invitations()->where('vendor_contact_id', $this->contact->id)->first(); | ||||
|  | ||||
| @ -11,11 +11,12 @@ | ||||
| 
 | ||||
| namespace App\Services\PurchaseOrder; | ||||
| 
 | ||||
| 
 | ||||
| use App\Jobs\Vendor\CreatePurchaseOrderPdf; | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Services\PurchaseOrder\ApplyNumber; | ||||
| use App\Services\PurchaseOrder\CreateInvitations; | ||||
| use App\Services\PurchaseOrder\GetPurchaseOrderPdf; | ||||
| use App\Services\PurchaseOrder\TriggeredActions; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| 
 | ||||
| class PurchaseOrderService | ||||
| @ -62,6 +63,13 @@ class PurchaseOrderService | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function triggeredActions($request) | ||||
|     { | ||||
|         $this->purchase_order = (new TriggeredActions($this->purchase_order->load('invitations'), $request))->run(); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function getPurchaseOrderPdf($contact = null) | ||||
|     { | ||||
|         return (new GetPurchaseOrderPdf($this->purchase_order, $contact))->run(); | ||||
| @ -81,6 +89,34 @@ class PurchaseOrderService | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public function touchPdf($force = false) | ||||
|     { | ||||
|         try { | ||||
|          | ||||
|             if($force){ | ||||
| 
 | ||||
|                 $this->purchase_order->invitations->each(function ($invitation) { | ||||
|                     CreatePurchaseOrderPdf::dispatchNow($invitation); | ||||
|                 }); | ||||
| 
 | ||||
|                 return $this; | ||||
|             } | ||||
| 
 | ||||
|             $this->purchase_order->invitations->each(function ($invitation) { | ||||
|                 CreatePurchaseOrderPdf::dispatch($invitation); | ||||
|             }); | ||||
|          | ||||
|         } | ||||
|         catch(\Exception $e){ | ||||
| 
 | ||||
|             nlog("failed creating purchase orders in Touch PDF"); | ||||
|          | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Saves the purchase order. | ||||
|      * @return \App\Models\PurchaseOrder object | ||||
|  | ||||
							
								
								
									
										65
									
								
								app/Services/PurchaseOrder/TriggeredActions.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								app/Services/PurchaseOrder/TriggeredActions.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,65 @@ | ||||
| <?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\Services\PurchaseOrder; | ||||
| 
 | ||||
| use App\Events\Invoice\InvoiceWasEmailed; | ||||
| use App\Events\PurchaseOrder\PurchaseOrderWasEmailed; | ||||
| use App\Jobs\Entity\EmailEntity; | ||||
| use App\Jobs\PurchaseOrder\PurchaseOrderEmail; | ||||
| use App\Models\Invoice; | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Services\AbstractService; | ||||
| use App\Utils\Ninja; | ||||
| use App\Utils\Traits\GeneratesCounter; | ||||
| use Illuminate\Http\Request; | ||||
| 
 | ||||
| class TriggeredActions extends AbstractService | ||||
| { | ||||
|     use GeneratesCounter; | ||||
| 
 | ||||
|     private $request; | ||||
| 
 | ||||
|     private $purchase_order; | ||||
| 
 | ||||
|     public function __construct(PurchaseOrder $purchase_order, Request $request) | ||||
|     { | ||||
|         $this->request = $request; | ||||
| 
 | ||||
|         $this->purchase_order = $purchase_order; | ||||
|     } | ||||
| 
 | ||||
|     public function run() | ||||
|     { | ||||
| 
 | ||||
|         if ($this->request->has('send_email') && $this->request->input('send_email') == 'true') { | ||||
|             $this->purchase_order->service()->markSent()->touchPdf()->save(); | ||||
|             $this->sendEmail(); | ||||
|         } | ||||
| 
 | ||||
|         if ($this->request->has('mark_sent') && $this->request->input('mark_sent') == 'true') { | ||||
|             $this->purchase_order = $this->purchase_order->service()->markSent()->touchPdf()->save(); | ||||
|         } | ||||
| 
 | ||||
|         // if ($this->request->has('cancel') && $this->request->input('cancel') == 'true') {
 | ||||
|         //     $this->purchase_order = $this->purchase_order->service()->handleCancellation()->save();
 | ||||
|         // }        
 | ||||
| 
 | ||||
|         return $this->purchase_order; | ||||
|     } | ||||
| 
 | ||||
|     private function sendEmail() | ||||
|     { | ||||
| 
 | ||||
|         PurchaseOrderEmail::dispatch($this->purchase_order, $this->purchase_order->company); | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -164,6 +164,7 @@ class SubscriptionService | ||||
| 
 | ||||
|         $recurring_invoice = $this->convertInvoiceToRecurring($client_contact->client_id); | ||||
|         $recurring_invoice->next_send_date = now()->addSeconds($this->subscription->trial_duration); | ||||
|         $recurring_invoice->next_send_date_client = now()->addSeconds($this->subscription->trial_duration); | ||||
|         $recurring_invoice->backup = 'is_trial'; | ||||
| 
 | ||||
|         if(array_key_exists('coupon', $data) && ($data['coupon'] == $this->subscription->promo_code) && $this->subscription->promo_discount > 0) | ||||
| @ -620,7 +621,9 @@ class SubscriptionService | ||||
|             $recurring_invoice = $this->convertInvoiceToRecurring($old_recurring_invoice->client_id); | ||||
|             $recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice); | ||||
|             $recurring_invoice->next_send_date = now()->format('Y-m-d'); | ||||
|             $recurring_invoice->next_send_date_client = now()->format('Y-m-d'); | ||||
|             $recurring_invoice->next_send_date = $recurring_invoice->nextSendDate(); | ||||
|             $recurring_invoice->next_send_date_client = $recurring_invoice->nextSendDateClient(); | ||||
| 
 | ||||
|             /* Start the recurring service */ | ||||
|             $recurring_invoice->service() | ||||
| @ -754,8 +757,9 @@ class SubscriptionService | ||||
|         $recurring_invoice->auto_bill_enabled =  $this->setAutoBillFlag($recurring_invoice->auto_bill); | ||||
|         $recurring_invoice->due_date_days = 'terms'; | ||||
|         $recurring_invoice->next_send_date = now()->format('Y-m-d'); | ||||
|         $recurring_invoice->next_send_date_client = now()->format('Y-m-d'); | ||||
|         $recurring_invoice->next_send_date =  $recurring_invoice->nextSendDate(); | ||||
| 
 | ||||
|         $recurring_invoice->next_send_date_client = $recurring_invoice->nextSendDateClient(); | ||||
|         return $recurring_invoice; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -58,7 +58,10 @@ class ZeroCostProduct extends AbstractService | ||||
| 
 | ||||
|             $recurring_invoice->next_send_date = now(); | ||||
|             $recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice); | ||||
|             $recurring_invoice->next_send_date = now(); | ||||
|             $recurring_invoice->next_send_date_client = now(); | ||||
|             $recurring_invoice->next_send_date = $recurring_invoice->nextSendDate(); | ||||
|             $recurring_invoice->next_send_date_client = $recurring_invoice->nextSendDateClient(); | ||||
| 
 | ||||
|             /* Start the recurring service */ | ||||
|             $recurring_invoice->service() | ||||
|  | ||||
| @ -86,6 +86,7 @@ class AccountTransformer extends EntityTransformer | ||||
|             'hosted_client_count' => (int) $account->hosted_client_count, | ||||
|             'hosted_company_count' => (int) $account->hosted_company_count, | ||||
|             'is_hosted' => (bool) Ninja::isHosted(), | ||||
|             'set_react_as_default_ap' => (bool) $account->set_react_as_default_ap | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -65,7 +65,7 @@ class ActivityTransformer extends EntityTransformer | ||||
|             'created_at' => (int) $activity->created_at, | ||||
|             'expense_id' => $activity->expense_id ? (string) $this->encodePrimaryKey($activity->expense_id) : '', | ||||
|             'is_system' => (bool) $activity->is_system, | ||||
|             'contact_id' => $activity->contact_id ? (string) $this->encodePrimaryKey($activity->contact_id) : '', | ||||
|             'contact_id' => $activity->client_contact_id ? (string) $this->encodePrimaryKey($activity->client_contact_id) : '', | ||||
|             'task_id' => $activity->task_id ? (string) $this->encodePrimaryKey($activity->task_id) : '', | ||||
|             'token_id' => $activity->token_id ? (string) $this->encodePrimaryKey($activity->token_id) : '', | ||||
|             'notes' => $activity->notes ? (string) $activity->notes : '', | ||||
|  | ||||
| @ -29,6 +29,7 @@ use App\Models\Payment; | ||||
| use App\Models\PaymentTerm; | ||||
| use App\Models\Product; | ||||
| use App\Models\Project; | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Models\Quote; | ||||
| use App\Models\RecurringExpense; | ||||
| use App\Models\RecurringInvoice; | ||||
| @ -39,6 +40,7 @@ use App\Models\TaskStatus; | ||||
| use App\Models\TaxRate; | ||||
| use App\Models\User; | ||||
| use App\Models\Webhook; | ||||
| use App\Transformers\PurchaseOrderTransformer; | ||||
| use App\Transformers\RecurringExpenseTransformer; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use stdClass; | ||||
| @ -95,6 +97,7 @@ class CompanyTransformer extends EntityTransformer | ||||
|         'task_statuses', | ||||
|         'subscriptions', | ||||
|         'recurring_expenses', | ||||
|         'purchase_orders', | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
| @ -391,4 +394,11 @@ class CompanyTransformer extends EntityTransformer | ||||
| 
 | ||||
|         return $this->includeCollection($company->subscriptions, $transformer, Subscription::class); | ||||
|     } | ||||
| 
 | ||||
|     public function includePurchaseOrders(Company $company) | ||||
|     { | ||||
|         $transformer = new PurchaseOrderTransformer($this->serializer); | ||||
| 
 | ||||
|         return $this->includeCollection($company->purchase_orders, $transformer, PurchaseOrder::class); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -265,6 +265,8 @@ trait MakesInvoiceValues | ||||
|      */ | ||||
|     public function transformLineItems($items, $table_type = '$product') :array | ||||
|     { | ||||
|         $entity = $this->client ? $this->client : $this->company; | ||||
| 
 | ||||
|         $data = []; | ||||
| 
 | ||||
|         if (! is_array($items)) { | ||||
| @ -294,23 +296,23 @@ trait MakesInvoiceValues | ||||
|             $data[$key][$table_type.'.item'] = is_null(optional($item)->item) ? $item->product_key : $item->item; | ||||
|             $data[$key][$table_type.'.service'] = is_null(optional($item)->service) ? $item->product_key : $item->service; | ||||
| 
 | ||||
|             $data[$key][$table_type.'.notes'] = Helpers::processReservedKeywords($item->notes, $this->client); | ||||
|             $data[$key][$table_type.'.description'] = Helpers::processReservedKeywords($item->notes, $this->client); | ||||
|             $data[$key][$table_type.'.notes'] = Helpers::processReservedKeywords($item->notes, $entity); | ||||
|             $data[$key][$table_type.'.description'] = Helpers::processReservedKeywords($item->notes, $entity); | ||||
|      | ||||
|             $data[$key][$table_type . ".{$_table_type}1"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}1", $item->custom_value1, $this->client); | ||||
|             $data[$key][$table_type . ".{$_table_type}2"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}2", $item->custom_value2, $this->client); | ||||
|             $data[$key][$table_type . ".{$_table_type}3"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}3", $item->custom_value3, $this->client); | ||||
|             $data[$key][$table_type . ".{$_table_type}4"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}4", $item->custom_value4, $this->client); | ||||
|             $data[$key][$table_type . ".{$_table_type}1"] = $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}1", $item->custom_value1, $entity); | ||||
|             $data[$key][$table_type . ".{$_table_type}2"] = $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}2", $item->custom_value2, $entity); | ||||
|             $data[$key][$table_type . ".{$_table_type}3"] = $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}3", $item->custom_value3, $entity); | ||||
|             $data[$key][$table_type . ".{$_table_type}4"] = $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}4", $item->custom_value4, $entity); | ||||
|              | ||||
|             if($item->quantity > 0 || $item->cost > 0){ | ||||
| 
 | ||||
|                 $data[$key][$table_type.'.quantity'] = Number::formatValueNoTrailingZeroes($item->quantity, $this->client->currency()); | ||||
|                 $data[$key][$table_type.'.quantity'] = Number::formatValueNoTrailingZeroes($item->quantity, $entity->currency()); | ||||
|                  | ||||
|                 $data[$key][$table_type.'.unit_cost'] = Number::formatMoneyNoRounding($item->cost, $this->client); | ||||
|                 $data[$key][$table_type.'.unit_cost'] = Number::formatMoneyNoRounding($item->cost, $entity); | ||||
| 
 | ||||
|                 $data[$key][$table_type.'.cost'] = Number::formatMoney($item->cost, $this->client); | ||||
|                 $data[$key][$table_type.'.cost'] = Number::formatMoney($item->cost, $entity); | ||||
| 
 | ||||
|                 $data[$key][$table_type.'.line_total'] =  Number::formatMoney($item->line_total, $this->client); | ||||
|                 $data[$key][$table_type.'.line_total'] =  Number::formatMoney($item->line_total, $entity); | ||||
| 
 | ||||
|             } | ||||
|             else { | ||||
| @ -326,13 +328,13 @@ trait MakesInvoiceValues | ||||
|             } | ||||
| 
 | ||||
|             if(property_exists($item, 'gross_line_total')) | ||||
|                 $data[$key][$table_type.'.gross_line_total'] =  ($item->gross_line_total == 0) ? '' :Number::formatMoney($item->gross_line_total, $this->client); | ||||
|                 $data[$key][$table_type.'.gross_line_total'] =  ($item->gross_line_total == 0) ? '' :Number::formatMoney($item->gross_line_total, $entity); | ||||
|             else | ||||
|                 $data[$key][$table_type.'.gross_line_total'] = ''; | ||||
|          | ||||
|             if (isset($item->discount) && $item->discount > 0) { | ||||
|                 if ($item->is_amount_discount) { | ||||
|                     $data[$key][$table_type.'.discount'] = Number::formatMoney($item->discount, $this->client); | ||||
|                     $data[$key][$table_type.'.discount'] = Number::formatMoney($item->discount, $entity); | ||||
|                 } else { | ||||
|                     $data[$key][$table_type.'.discount'] = floatval($item->discount).'%'; | ||||
|                 } | ||||
| @ -376,13 +378,14 @@ trait MakesInvoiceValues | ||||
|     private function makeLineTaxes() :string | ||||
|     { | ||||
|         $tax_map = $this->calc()->getTaxMap(); | ||||
|         $entity = $this->client ? $this->client : $this->company; | ||||
| 
 | ||||
|         $data = ''; | ||||
| 
 | ||||
|         foreach ($tax_map as $tax) { | ||||
|             $data .= '<tr class="line_taxes">'; | ||||
|             $data .= '<td>'.$tax['name'].'</td>'; | ||||
|             $data .= '<td>'.Number::formatMoney($tax['total'], $this->client).'</td></tr>'; | ||||
|             $data .= '<td>'.Number::formatMoney($tax['total'], $entity).'</td></tr>'; | ||||
|         } | ||||
| 
 | ||||
|         return $data; | ||||
| @ -395,6 +398,7 @@ trait MakesInvoiceValues | ||||
|     private function makeTotalTaxes() :string | ||||
|     { | ||||
|         $data = ''; | ||||
|         $entity = $this->client ? $this->client : $this->company; | ||||
| 
 | ||||
|         if (! $this->calc()->getTotalTaxMap()) { | ||||
|             return $data; | ||||
| @ -403,7 +407,7 @@ trait MakesInvoiceValues | ||||
|         foreach ($this->calc()->getTotalTaxMap() as $tax) { | ||||
|             $data .= '<tr class="total_taxes">'; | ||||
|             $data .= '<td>'.$tax['name'].'</td>'; | ||||
|             $data .= '<td>'.Number::formatMoney($tax['total'], $this->client).'</td></tr>'; | ||||
|             $data .= '<td>'.Number::formatMoney($tax['total'], $entity).'</td></tr>'; | ||||
|         } | ||||
| 
 | ||||
|         return $data; | ||||
| @ -427,13 +431,14 @@ trait MakesInvoiceValues | ||||
|     private function totalTaxValues() :string | ||||
|     { | ||||
|         $data = ''; | ||||
|         $entity = $this->client ? $this->client : $this->company; | ||||
| 
 | ||||
|         if (! $this->calc()->getTotalTaxMap()) { | ||||
|             return $data; | ||||
|         } | ||||
| 
 | ||||
|         foreach ($this->calc()->getTotalTaxMap() as $tax) { | ||||
|             $data .= '<span>'.Number::formatMoney($tax['total'], $this->client).'</span>'; | ||||
|             $data .= '<span>'.Number::formatMoney($tax['total'], $entity).'</span>'; | ||||
|         } | ||||
| 
 | ||||
|         return $data; | ||||
| @ -455,11 +460,12 @@ trait MakesInvoiceValues | ||||
|     private function lineTaxValues() :string | ||||
|     { | ||||
|         $tax_map = $this->calc()->getTaxMap(); | ||||
|         $entity = $this->client ? $this->client : $this->company; | ||||
| 
 | ||||
|         $data = ''; | ||||
| 
 | ||||
|         foreach ($tax_map as $tax) { | ||||
|             $data .= '<span>'.Number::formatMoney($tax['total'], $this->client).'</span>'; | ||||
|             $data .= '<span>'.Number::formatMoney($tax['total'], $entity).'</span>'; | ||||
|         } | ||||
| 
 | ||||
|         return $data; | ||||
| @ -481,7 +487,8 @@ trait MakesInvoiceValues | ||||
|      */ | ||||
|     public function generateCustomCSS() :string | ||||
|     { | ||||
|         $settings = $this->client->getMergedSettings(); | ||||
| 
 | ||||
|         $settings = $this->client ? $this->client->getMergedSettings() :  $this->company->settings; | ||||
| 
 | ||||
|         $header_and_footer = ' | ||||
| .header, .header-space { | ||||
|  | ||||
| @ -15,6 +15,7 @@ use App\Models\Client; | ||||
| use App\Models\Credit; | ||||
| use App\Models\Invoice; | ||||
| use App\Models\Payment; | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Models\Quote; | ||||
| 
 | ||||
| /** | ||||
| @ -99,7 +100,10 @@ trait UserNotifies | ||||
|                 break; | ||||
|             case ($entity instanceof Credit): | ||||
|                 return array_merge($required_permissions, ["all_notifications","all_user_notifications","credit_created_user","credit_sent_user","credit_viewed_user"]); | ||||
|                 break;             | ||||
|                 break; | ||||
|             case ($entity instanceof PurchaseOrder): | ||||
|                 return array_merge($required_permissions, ["all_notifications","all_user_notifications","purchase_order_created_user","purchase_order_sent_user","purchase_order_viewed_user"]); | ||||
|                 break;       | ||||
|             default: | ||||
|                 return []; | ||||
|                 break; | ||||
| @ -122,7 +126,10 @@ trait UserNotifies | ||||
|                 break; | ||||
|             case ($entity instanceof Credit): | ||||
|                 return array_diff($required_permissions, ["all_user_notifications","credit_created_user","credit_sent_user","credit_viewed_user"]); | ||||
|                 break;             | ||||
|                 break; | ||||
|             case ($entity instanceof PurchaseOrder): | ||||
|                 return array_diff($required_permissions, ["all_user_notifications","purchase_order_created_user","purchase_order_sent_user","purchase_order_viewed_user"]); | ||||
|                 break;                 | ||||
|             default: | ||||
|                 // code...
 | ||||
|                 break; | ||||
|  | ||||
| @ -134,20 +134,15 @@ class VendorHtmlEngine | ||||
| 
 | ||||
|         $data['$entity.datetime'] = ['value' => $this->formatDatetime($this->entity->created_at, $this->company->date_format(), $this->company->locale()), 'label' => ctrans('texts.date')]; | ||||
| 
 | ||||
|         $data['$payment_button'] = ['value' => '<a class="button" href="'.$this->invitation->getPaymentLink().'">'.ctrans('texts.pay_now').'</a>', 'label' => ctrans('texts.pay_now')]; | ||||
|         $data['$payment_link'] = ['value' => $this->invitation->getPaymentLink(), 'label' => ctrans('texts.pay_now')]; | ||||
| 
 | ||||
| 
 | ||||
|         $data['$entity'] = ['value' => '', 'label' => ctrans('texts.purchase_order')]; | ||||
|         $data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.purchase_order_number')]; | ||||
|         $data['$number_short'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.purchase_order_number_short')]; | ||||
|         $data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms), $this->company) ?: '', 'label' => ctrans('texts.invoice_terms')]; | ||||
|         $data['$terms'] = &$data['$entity.terms']; | ||||
|         $data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_invoice').'</a>', 'label' => ctrans('texts.view_invoice')]; | ||||
|         $data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_purchase_order').'</a>', 'label' => ctrans('texts.view_purchase_order')]; | ||||
|         $data['$viewLink'] = &$data['$view_link']; | ||||
|         $data['$viewButton'] = &$data['$view_link']; | ||||
|         $data['$view_button'] = &$data['$view_link']; | ||||
|         $data['$paymentButton'] = &$data['$payment_button']; | ||||
|         $data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')]; | ||||
|         $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.date')]; | ||||
| 
 | ||||
| @ -390,11 +385,6 @@ class VendorHtmlEngine | ||||
|         $data['$autoBill'] = ['value' => ctrans('texts.auto_bill_notification_placeholder'), 'label' => '']; | ||||
|         $data['$auto_bill'] = &$data['$autoBill']; | ||||
| 
 | ||||
|         /*Payment Aliases*/ | ||||
|         $data['$paymentLink'] = &$data['$payment_link']; | ||||
|         $data['$payment_url'] = &$data['$payment_link']; | ||||
|         $data['$portalButton'] = &$data['$paymentLink']; | ||||
| 
 | ||||
|         $data['$dir'] = ['value' => optional($this->company->language())->locale === 'ar' ? 'rtl' : 'ltr', 'label' => '']; | ||||
|         $data['$dir_text_align'] = ['value' => optional($this->company->language())->locale === 'ar' ? 'right' : 'left', 'label' => '']; | ||||
| 
 | ||||
|  | ||||
| @ -11,7 +11,12 @@ | ||||
|         "Credit card billing", | ||||
|         "projects", | ||||
|         "tasks", | ||||
|         "freelancer" | ||||
|         "freelancer", | ||||
|         "quotes", | ||||
|         "purchase orders", | ||||
|         "stripe billing", | ||||
|         "invoices", | ||||
|         "subscriptions" | ||||
|     ], | ||||
|     "license": "Elastic License", | ||||
|     "authors": [ | ||||
| @ -37,7 +42,7 @@ | ||||
|         "bacon/bacon-qr-code": "^2.0", | ||||
|         "beganovich/snappdf": "^1.7", | ||||
|         "braintree/braintree_php": "^6.0", | ||||
|         "checkout/checkout-sdk-php": "^1.0", | ||||
|         "checkout/checkout-sdk-php": "^2.5", | ||||
|         "cleverit/ubl_invoice": "^1.3", | ||||
|         "coconutcraig/laravel-postmark": "^2.10", | ||||
|         "doctrine/dbal": "^3.0", | ||||
| @ -77,6 +82,8 @@ | ||||
|         "sentry/sentry-laravel": "^2", | ||||
|         "setasign/fpdf": "^1.8", | ||||
|         "setasign/fpdi": "^2.3", | ||||
|         "socialiteproviders/apple": "^5.2", | ||||
|         "socialiteproviders/microsoft": "^4.1", | ||||
|         "square/square": "13.0.0.20210721", | ||||
|         "stripe/stripe-php": "^7.50", | ||||
|         "symfony/http-client": "^5.2", | ||||
|  | ||||
							
								
								
									
										1012
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1012
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -56,6 +56,10 @@ return [ | ||||
|             'driver' => 'session', | ||||
|             'provider' => 'contacts', | ||||
|         ], | ||||
|         'vendor' => [ | ||||
|             'driver' => 'session', | ||||
|             'provider' => 'vendors', | ||||
|         ], | ||||
|     ], | ||||
| 
 | ||||
|     /* | ||||
| @ -85,6 +89,11 @@ return [ | ||||
|             'driver' => 'eloquent', | ||||
|             'model' => App\Models\ClientContact::class, | ||||
|         ], | ||||
|         'vendors' => [ | ||||
|             'driver' => 'eloquent', | ||||
|             'model' => App\Models\VendorContact::class, | ||||
|         ], | ||||
| 
 | ||||
| 
 | ||||
|         // 'users' => [
 | ||||
|         //     'driver' => 'database',
 | ||||
| @ -120,6 +129,11 @@ return [ | ||||
|             'table' => 'password_resets', | ||||
|             'expire' => 60, | ||||
|         ], | ||||
|         'vendors' => [ | ||||
|             'provider' => 'vendors', | ||||
|             'table' => 'password_resets', | ||||
|             'expire' => 60, | ||||
|         ], | ||||
|     ], | ||||
| 
 | ||||
|     /* | ||||
|  | ||||
| @ -14,8 +14,8 @@ return [ | ||||
|     'require_https' => env('REQUIRE_HTTPS', true), | ||||
|     'app_url' => rtrim(env('APP_URL', ''), '/'), | ||||
|     'app_domain' => env('APP_DOMAIN', 'invoicing.co'), | ||||
|     'app_version' => '5.3.98', | ||||
|     'app_tag' => '5.3.98', | ||||
|     'app_version' => '5.4.0', | ||||
|     'app_tag' => '5.4.0', | ||||
|     'minimum_client_version' => '5.0.16', | ||||
|     'terms_version' => '1.0.1', | ||||
|     'api_secret' => env('API_SECRET', ''), | ||||
|  | ||||
| @ -80,4 +80,14 @@ return [ | ||||
|     'postmark' => [ | ||||
|         'token' => env('POSTMARK_SECRET'), | ||||
|     ], | ||||
|     'microsoft' => [ | ||||
|         'client_id' => env('MICROSOFT_CLIENT_ID'), | ||||
|         'client_secret' => env('MICROSOFT_CLIENT_SECRET'), | ||||
|         'redirect' => env('MICROSOFT_REDIRECT_URI') | ||||
|     ], | ||||
|     'apple' => [ | ||||
|         'client_id' => env('APPLE_CLIENT_ID'), | ||||
|         'client_secret' => env('APPLE_CLIENT_SECRET'), | ||||
|         'redirect' => env('APPLE_REDIRECT_URI') | ||||
|     ], | ||||
| ]; | ||||
|  | ||||
| @ -0,0 +1,37 @@ | ||||
| <?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 | ||||
|  */ | ||||
| use Illuminate\Database\Migrations\Migration; | ||||
| use Illuminate\Database\Schema\Blueprint; | ||||
| use Illuminate\Support\Facades\Schema; | ||||
| 
 | ||||
| class SetAccountFlagForReact extends Migration | ||||
| { | ||||
|     /** | ||||
|      * Run the migrations. | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function up() | ||||
|     { | ||||
|         Illuminate\Support\Facades\Artisan::call('ninja:design-update'); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Reverse the migrations. | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function down() | ||||
|     { | ||||
|         //
 | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| <?php | ||||
| 
 | ||||
| use Illuminate\Database\Migrations\Migration; | ||||
| use Illuminate\Database\Schema\Blueprint; | ||||
| use Illuminate\Support\Facades\Schema; | ||||
| 
 | ||||
| class AddReactSwitchingFlag extends Migration | ||||
| { | ||||
|     /** | ||||
|      * Run the migrations. | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function up() | ||||
|     { | ||||
|         Schema::table('accounts', function (Blueprint $table) { | ||||
|             $table->boolean('set_react_as_default_ap')->default(0); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Reverse the migrations. | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function down() | ||||
|     { | ||||
|         //
 | ||||
|     } | ||||
| } | ||||
							
								
								
									
										2
									
								
								public/css/app.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								public/css/app.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										6
									
								
								public/flutter_service_worker.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								public/flutter_service_worker.js
									
									
									
									
										vendored
									
									
								
							| @ -5,9 +5,9 @@ const CACHE_NAME = 'flutter-app-cache'; | ||||
| const RESOURCES = { | ||||
|   "icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed", | ||||
| "icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35", | ||||
| "/": "f4932ba24bfa72c97f7578b8951891c2", | ||||
| "main.dart.js": "68a04477f6ce39dcf894f583120e1c46", | ||||
| "version.json": "3afb81924daf4f751571755436069115", | ||||
| "/": "2e739a78eec983322924f724ebfa09ba", | ||||
| "main.dart.js": "fa4a0263712be1ce1df7d59ca0ede10e", | ||||
| "version.json": "d72bd323e3b8e22ce5acdc247f4e6f62", | ||||
| "favicon.png": "dca91c54388f52eded692718d5a98b8b", | ||||
| "flutter.js": "0816e65a103ba8ba51b174eeeeb2cb67", | ||||
| "favicon.ico": "51636d3a390451561744c42188ccd628", | ||||
|  | ||||
							
								
								
									
										2
									
								
								public/js/clients/purchase_orders/accept.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								public/js/clients/purchase_orders/accept.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| /*! For license information please see accept.js.LICENSE.txt */ | ||||
| (()=>{function e(e,t){for(var n=0;n<t.length;n++){var a=t[n];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),Object.defineProperty(e,a.key,a)}}var t=function(){function t(e,n){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),this.shouldDisplaySignature=e,this.shouldDisplayTerms=n,this.termsAccepted=!1}var n,a,r;return n=t,(a=[{key:"submitForm",value:function(){document.getElementById("approve-form").submit()}},{key:"displaySignature",value:function(){document.getElementById("displaySignatureModal").removeAttribute("style");var e=new SignaturePad(document.getElementById("signature-pad"),{penColor:"rgb(0, 0, 0)"});this.signaturePad=e}},{key:"displayTerms",value:function(){document.getElementById("displayTermsModal").removeAttribute("style")}},{key:"handle",value:function(){var e=this;document.getElementById("approve-button").addEventListener("click",(function(){e.shouldDisplaySignature&&e.shouldDisplayTerms&&(e.displaySignature(),document.getElementById("signature-next-step").addEventListener("click",(function(){e.displayTerms(),document.getElementById("accept-terms-button").addEventListener("click",(function(){document.querySelector('input[name="signature"').value=e.signaturePad.toDataURL(),e.termsAccepted=!0,e.submitForm()}))}))),e.shouldDisplaySignature&&!e.shouldDisplayTerms&&(e.displaySignature(),document.getElementById("signature-next-step").addEventListener("click",(function(){document.querySelector('input[name="signature"').value=e.signaturePad.toDataURL(),e.submitForm()}))),!e.shouldDisplaySignature&&e.shouldDisplayTerms&&(e.displayTerms(),document.getElementById("accept-terms-button").addEventListener("click",(function(){e.termsAccepted=!0,e.submitForm()}))),e.shouldDisplaySignature||e.shouldDisplayTerms||e.submitForm()}))}}])&&e(n.prototype,a),r&&e(n,r),Object.defineProperty(n,"prototype",{writable:!1}),t}(),n=document.querySelector('meta[name="require-purchase_order-signature"]').content,a=document.querySelector('meta[name="show-purchase_order-terms"]').content;new t(Boolean(+n),Boolean(+a)).handle()})(); | ||||
							
								
								
									
										9
									
								
								public/js/clients/purchase_orders/accept.js.LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								public/js/clients/purchase_orders/accept.js.LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com) | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license  | ||||
|  */ | ||||
							
								
								
									
										2
									
								
								public/js/clients/purchase_orders/action-selectors.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								public/js/clients/purchase_orders/action-selectors.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| /*! For license information please see action-selectors.js.LICENSE.txt */ | ||||
| (()=>{function e(e,n){var r="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!r){if(Array.isArray(e)||(r=function(e,n){if(!e)return;if("string"==typeof e)return t(e,n);var r=Object.prototype.toString.call(e).slice(8,-1);"Object"===r&&e.constructor&&(r=e.constructor.name);if("Map"===r||"Set"===r)return Array.from(e);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return t(e,n)}(e))||n&&e&&"number"==typeof e.length){r&&(e=r);var o=0,c=function(){};return{s:c,n:function(){return o>=e.length?{done:!0}:{done:!1,value:e[o++]}},e:function(e){throw e},f:c}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,a=!0,l=!1;return{s:function(){r=r.call(e)},n:function(){var e=r.next();return a=e.done,e},e:function(e){l=!0,i=e},f:function(){try{a||null==r.return||r.return()}finally{if(l)throw i}}}}function t(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(new(function(){function t(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),this.parentElement=document.querySelector(".form-check-parent"),this.parentForm=document.getElementById("bulkActions")}var r,o,c;return r=t,o=[{key:"watchCheckboxes",value:function(e){var t=this;document.querySelectorAll(".child-hidden-input").forEach((function(e){return e.remove()})),document.querySelectorAll(".form-check-child").forEach((function(n){e.checked?(n.checked=e.checked,t.processChildItem(n,document.getElementById("bulkActions"))):(n.checked=!1,document.querySelectorAll(".child-hidden-input").forEach((function(e){return e.remove()})))}))}},{key:"processChildItem",value:function(t,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(r.hasOwnProperty("single")&&document.querySelectorAll(".child-hidden-input").forEach((function(e){return e.remove()})),!1!==t.checked){var o=document.createElement("INPUT");o.setAttribute("name","purchase_orders[]"),o.setAttribute("value",t.dataset.value),o.setAttribute("class","child-hidden-input"),o.hidden=!0,n.append(o)}else{var c,i=document.querySelectorAll("input.child-hidden-input"),a=e(i);try{for(a.s();!(c=a.n()).done;){var l=c.value;l.value==t.dataset.value&&l.remove()}}catch(e){a.e(e)}finally{a.f()}}}},{key:"handle",value:function(){var t=this;this.parentElement.addEventListener("click",(function(){t.watchCheckboxes(t.parentElement)}));var n,r=e(document.querySelectorAll(".form-check-child"));try{var o=function(){var e=n.value;e.addEventListener("click",(function(){t.processChildItem(e,t.parentForm)}))};for(r.s();!(n=r.n()).done;)o()}catch(e){r.e(e)}finally{r.f()}}}],o&&n(r.prototype,o),c&&n(r,c),Object.defineProperty(r,"prototype",{writable:!1}),t}())).handle()})(); | ||||
| @ -0,0 +1,9 @@ | ||||
| /** | ||||
|  * Invoice Ninja (https://invoiceninja.com) | ||||
|  * | ||||
|  * @link https://github.com/invoiceninja/invoiceninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license  | ||||
|  */ | ||||
							
								
								
									
										297796
									
								
								public/main.dart.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										297796
									
								
								public/main.dart.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										295618
									
								
								public/main.foss.dart.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										295618
									
								
								public/main.foss.dart.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user