mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-10-31 06:27:34 -04:00 
			
		
		
		
	
						commit
						bcd3383b0b
					
				
							
								
								
									
										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.100 | ||||
| 5.4.0 | ||||
							
								
								
									
										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 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); | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										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,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(); | ||||
|  | ||||
| @ -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 | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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'); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										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'); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
| @ -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,7 +58,7 @@ class Account extends BaseModel | ||||
|         'utm_content', | ||||
|         'user_agent', | ||||
|         'platform', | ||||
|         // 'set_react_as_default_ap',
 | ||||
|         'set_react_as_default_ap', | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
| @ -75,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'; | ||||
| @ -88,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'; | ||||
| @ -163,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: | ||||
| @ -469,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', | ||||
|  | ||||
| @ -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(); | ||||
|  | ||||
| @ -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() | ||||
|     { | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -245,7 +245,7 @@ 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"); | ||||
|                  | ||||
|  | ||||
| @ -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; | ||||
| @ -471,6 +474,10 @@ class EventServiceProvider extends ServiceProvider | ||||
|         PurchaseOrderWasViewed::class => [ | ||||
|             PurchaseOrderViewedActivity::class, | ||||
|         ], | ||||
|         PurchaseOrderWasAccepted::class => [ | ||||
|             PurchaseOrderAcceptedActivity::class, | ||||
|             PurchaseOrderAcceptedNotification::class | ||||
|         ], | ||||
|         CompanyDocumentsDeleted::class => [ | ||||
|             DeleteCompanyDocuments::class, | ||||
|         ], | ||||
|  | ||||
| @ -50,6 +50,8 @@ class RouteServiceProvider extends ServiceProvider | ||||
| 
 | ||||
|         $this->mapContactApiRoutes(); | ||||
| 
 | ||||
|         $this->mapVendorsApiRoutes(); | ||||
| 
 | ||||
|         $this->mapClientApiRoutes(); | ||||
| 
 | ||||
|         $this->mapShopApiRoutes(); | ||||
| @ -125,7 +127,7 @@ class RouteServiceProvider extends ServiceProvider | ||||
|     protected function mapVendorsApiRoutes() | ||||
|     { | ||||
|         Route::prefix('') | ||||
|             ->middleware('vendor') | ||||
|             ->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'); | ||||
|             } | ||||
| 
 | ||||
|  | ||||
| @ -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,7 +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
 | ||||
|             'set_react_as_default_ap' => (bool) $account->set_react_as_default_ap | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -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); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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", | ||||
|  | ||||
							
								
								
									
										103
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										103
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							| @ -4,7 +4,7 @@ | ||||
|         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", | ||||
|         "This file is @generated automatically" | ||||
|     ], | ||||
|     "content-hash": "c9278efe297c252de6bc0b5a48540c0b", | ||||
|     "content-hash": "6845489fdc254427c4536e22f025ff51", | ||||
|     "packages": [ | ||||
|         { | ||||
|             "name": "afosto/yaac", | ||||
| @ -110,16 +110,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "apimatic/unirest-php", | ||||
|             "version": "2.2.2", | ||||
|             "version": "2.3.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/apimatic/unirest-php.git", | ||||
|                 "reference": "a45c4c71a1ea3659b118042a67cc1b6486bcf03a" | ||||
|                 "reference": "52e226fb3b7081dc9ef64aee876142a240a5f0f9" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/apimatic/unirest-php/zipball/a45c4c71a1ea3659b118042a67cc1b6486bcf03a", | ||||
|                 "reference": "a45c4c71a1ea3659b118042a67cc1b6486bcf03a", | ||||
|                 "url": "https://api.github.com/repos/apimatic/unirest-php/zipball/52e226fb3b7081dc9ef64aee876142a240a5f0f9", | ||||
|                 "reference": "52e226fb3b7081dc9ef64aee876142a240a5f0f9", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @ -168,9 +168,9 @@ | ||||
|             "support": { | ||||
|                 "email": "opensource@apimatic.io", | ||||
|                 "issues": "https://github.com/apimatic/unirest-php/issues", | ||||
|                 "source": "https://github.com/apimatic/unirest-php/tree/2.2.2" | ||||
|                 "source": "https://github.com/apimatic/unirest-php/tree/2.3.0" | ||||
|             }, | ||||
|             "time": "2022-03-24T08:19:20+00:00" | ||||
|             "time": "2022-06-15T08:29:49+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "asm/php-ansible", | ||||
| @ -434,16 +434,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "aws/aws-sdk-php", | ||||
|             "version": "3.225.2", | ||||
|             "version": "3.225.5", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/aws/aws-sdk-php.git", | ||||
|                 "reference": "f846724ad842916061127d20da4fe4e129f7d4b8" | ||||
|                 "reference": "09b404c6b80b9c31be15fa245e647a2f9fb5e733" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f846724ad842916061127d20da4fe4e129f7d4b8", | ||||
|                 "reference": "f846724ad842916061127d20da4fe4e129f7d4b8", | ||||
|                 "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/09b404c6b80b9c31be15fa245e647a2f9fb5e733", | ||||
|                 "reference": "09b404c6b80b9c31be15fa245e647a2f9fb5e733", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @ -451,7 +451,7 @@ | ||||
|                 "ext-json": "*", | ||||
|                 "ext-pcre": "*", | ||||
|                 "ext-simplexml": "*", | ||||
|                 "guzzlehttp/guzzle": "^5.3.3 || ^6.5.6 || ^7.4.3", | ||||
|                 "guzzlehttp/guzzle": "^5.3.3 || ^6.2.1 || ^7.0", | ||||
|                 "guzzlehttp/promises": "^1.4.0", | ||||
|                 "guzzlehttp/psr7": "^1.7.0 || ^2.1.1", | ||||
|                 "mtdowling/jmespath.php": "^2.6", | ||||
| @ -519,9 +519,9 @@ | ||||
|             "support": { | ||||
|                 "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", | ||||
|                 "issues": "https://github.com/aws/aws-sdk-php/issues", | ||||
|                 "source": "https://github.com/aws/aws-sdk-php/tree/3.225.2" | ||||
|                 "source": "https://github.com/aws/aws-sdk-php/tree/3.225.5" | ||||
|             }, | ||||
|             "time": "2022-06-10T19:03:26+00:00" | ||||
|             "time": "2022-06-15T19:35:13+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "bacon/bacon-qr-code", | ||||
| @ -741,28 +741,37 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "checkout/checkout-sdk-php", | ||||
|             "version": "1.0.19", | ||||
|             "version": "2.5.1", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/checkout/checkout-sdk-php.git", | ||||
|                 "reference": "c2c323ea2f95f74bff3055c42801e7ce22999791" | ||||
|                 "reference": "097b862487f7583fd0fab47a08e3dc0800f5c3e4" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/checkout/checkout-sdk-php/zipball/c2c323ea2f95f74bff3055c42801e7ce22999791", | ||||
|                 "reference": "c2c323ea2f95f74bff3055c42801e7ce22999791", | ||||
|                 "url": "https://api.github.com/repos/checkout/checkout-sdk-php/zipball/097b862487f7583fd0fab47a08e3dc0800f5c3e4", | ||||
|                 "reference": "097b862487f7583fd0fab47a08e3dc0800f5c3e4", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
|                 "php": ">=5.4.0" | ||||
|                 "ext-fileinfo": "*", | ||||
|                 "ext-json": "*", | ||||
|                 "guzzlehttp/guzzle": "^6.5 || ^7.4", | ||||
|                 "monolog/monolog": "^1.27 || ^2.4", | ||||
|                 "php": ">=5.6.0" | ||||
|             }, | ||||
|             "require-dev": { | ||||
|                 "phpunit/phpunit": "^6" | ||||
|                 "mockery/mockery": "^1.3 || ^1.4", | ||||
|                 "phpstan/phpstan": "^1.2", | ||||
|                 "phpunit/phpunit": "^5.7 || ^9.0", | ||||
|                 "smgladkovskiy/phpcs-git-pre-commit": "dev-master", | ||||
|                 "squizlabs/php_codesniffer": "^3.3", | ||||
|                 "symfony/phpunit-bridge": "^5.2 || ^6.0" | ||||
|             }, | ||||
|             "type": "library", | ||||
|             "autoload": { | ||||
|                 "psr-4": { | ||||
|                     "Checkout\\": "src/" | ||||
|                     "Checkout\\": "lib/Checkout" | ||||
|                 } | ||||
|             }, | ||||
|             "notification-url": "https://packagist.org/downloads/", | ||||
| @ -772,7 +781,7 @@ | ||||
|             "authors": [ | ||||
|                 { | ||||
|                     "name": "Checkout.com", | ||||
|                     "email": "platforms@checkout.com", | ||||
|                     "email": "integrations@checkout.com", | ||||
|                     "homepage": "https://github.com/checkout/checkout-sdk-php/graphs/contributors" | ||||
|                 } | ||||
|             ], | ||||
| @ -794,9 +803,9 @@ | ||||
|             ], | ||||
|             "support": { | ||||
|                 "issues": "https://github.com/checkout/checkout-sdk-php/issues", | ||||
|                 "source": "https://github.com/checkout/checkout-sdk-php/tree/1.0.19" | ||||
|                 "source": "https://github.com/checkout/checkout-sdk-php/tree/2.5.1" | ||||
|             }, | ||||
|             "time": "2021-11-19T15:08:38+00:00" | ||||
|             "time": "2022-06-13T00:23:23+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "cleverit/ubl_invoice", | ||||
| @ -2285,16 +2294,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "google/apiclient-services", | ||||
|             "version": "v0.252.0", | ||||
|             "version": "v0.253.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/googleapis/google-api-php-client-services.git", | ||||
|                 "reference": "9941c959f6a1f781e49019b78f453d54554dff73" | ||||
|                 "reference": "70c62b17f7821526cb52c6f125254dc51f256109" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/9941c959f6a1f781e49019b78f453d54554dff73", | ||||
|                 "reference": "9941c959f6a1f781e49019b78f453d54554dff73", | ||||
|                 "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/70c62b17f7821526cb52c6f125254dc51f256109", | ||||
|                 "reference": "70c62b17f7821526cb52c6f125254dc51f256109", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @ -2323,22 +2332,22 @@ | ||||
|             ], | ||||
|             "support": { | ||||
|                 "issues": "https://github.com/googleapis/google-api-php-client-services/issues", | ||||
|                 "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.252.0" | ||||
|                 "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.253.0" | ||||
|             }, | ||||
|             "time": "2022-06-06T01:20:11+00:00" | ||||
|             "time": "2022-06-13T01:06:12+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "google/auth", | ||||
|             "version": "v1.21.0", | ||||
|             "version": "v1.21.1", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/googleapis/google-auth-library-php.git", | ||||
|                 "reference": "73392bad2eb6852eea9084b6bbdec752515cb849" | ||||
|                 "reference": "aa3b9ca29258ac6347ce3c8937a2418c5d78f840" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/73392bad2eb6852eea9084b6bbdec752515cb849", | ||||
|                 "reference": "73392bad2eb6852eea9084b6bbdec752515cb849", | ||||
|                 "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/aa3b9ca29258ac6347ce3c8937a2418c5d78f840", | ||||
|                 "reference": "aa3b9ca29258ac6347ce3c8937a2418c5d78f840", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @ -2381,9 +2390,9 @@ | ||||
|             "support": { | ||||
|                 "docs": "https://googleapis.github.io/google-auth-library-php/main/", | ||||
|                 "issues": "https://github.com/googleapis/google-auth-library-php/issues", | ||||
|                 "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.21.0" | ||||
|                 "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.21.1" | ||||
|             }, | ||||
|             "time": "2022-04-13T20:35:52+00:00" | ||||
|             "time": "2022-05-16T19:34:15+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "graham-campbell/result-type", | ||||
| @ -6016,16 +6025,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "paragonie/constant_time_encoding", | ||||
|             "version": "v2.6.1", | ||||
|             "version": "v2.6.3", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/paragonie/constant_time_encoding.git", | ||||
|                 "reference": "d6e1d5d0fb2458dfdd7018ec2f74be120353a3b9" | ||||
|                 "reference": "58c3f47f650c94ec05a151692652a868995d2938" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d6e1d5d0fb2458dfdd7018ec2f74be120353a3b9", | ||||
|                 "reference": "d6e1d5d0fb2458dfdd7018ec2f74be120353a3b9", | ||||
|                 "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", | ||||
|                 "reference": "58c3f47f650c94ec05a151692652a868995d2938", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @ -6079,7 +6088,7 @@ | ||||
|                 "issues": "https://github.com/paragonie/constant_time_encoding/issues", | ||||
|                 "source": "https://github.com/paragonie/constant_time_encoding" | ||||
|             }, | ||||
|             "time": "2022-06-11T00:43:46+00:00" | ||||
|             "time": "2022-06-14T06:56:20+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "paragonie/random_compat", | ||||
| @ -6815,16 +6824,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "pragmarx/google2fa", | ||||
|             "version": "8.0.0", | ||||
|             "version": "v8.0.1", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/antonioribeiro/google2fa.git", | ||||
|                 "reference": "26c4c5cf30a2844ba121760fd7301f8ad240100b" | ||||
|                 "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/26c4c5cf30a2844ba121760fd7301f8ad240100b", | ||||
|                 "reference": "26c4c5cf30a2844ba121760fd7301f8ad240100b", | ||||
|                 "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/80c3d801b31fe165f8fe99ea085e0a37834e1be3", | ||||
|                 "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @ -6861,9 +6870,9 @@ | ||||
|             ], | ||||
|             "support": { | ||||
|                 "issues": "https://github.com/antonioribeiro/google2fa/issues", | ||||
|                 "source": "https://github.com/antonioribeiro/google2fa/tree/8.0.0" | ||||
|                 "source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.1" | ||||
|             }, | ||||
|             "time": "2020-04-05T10:47:18+00:00" | ||||
|             "time": "2022-06-13T21:57:56+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "predis/predis", | ||||
|  | ||||
| @ -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.100', | ||||
|     'app_tag' => '5.3.100', | ||||
|     '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', ''), | ||||
|  | ||||
| @ -23,9 +23,6 @@ class SetAccountFlagForReact extends Migration | ||||
|     { | ||||
|         Illuminate\Support\Facades\Artisan::call('ninja:design-update'); | ||||
| 
 | ||||
|         // Schema::table('accounts', function (Blueprint $table) {
 | ||||
|         //     $table->boolean('set_react_as_default_ap')->default(0);
 | ||||
|         // });
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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
											
										
									
								
							
							
								
								
									
										4
									
								
								public/flutter_service_worker.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								public/flutter_service_worker.js
									
									
									
									
										vendored
									
									
								
							| @ -5,8 +5,8 @@ const CACHE_NAME = 'flutter-app-cache'; | ||||
| const RESOURCES = { | ||||
|   "icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed", | ||||
| "icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35", | ||||
| "/": "94862dc60be2e82c49106de806115c42", | ||||
| "main.dart.js": "bb75daec9d3cdf8374011435e63376d2", | ||||
| "/": "2e739a78eec983322924f724ebfa09ba", | ||||
| "main.dart.js": "fa4a0263712be1ce1df7d59ca0ede10e", | ||||
| "version.json": "d72bd323e3b8e22ce5acdc247f4e6f62", | ||||
| "favicon.png": "dca91c54388f52eded692718d5a98b8b", | ||||
| "flutter.js": "0816e65a103ba8ba51b174eeeeb2cb67", | ||||
|  | ||||
							
								
								
									
										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  | ||||
|  */ | ||||
							
								
								
									
										295831
									
								
								public/main.dart.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										295831
									
								
								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
											
										
									
								
							
							
								
								
									
										292909
									
								
								public/main.foss.dart.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										292909
									
								
								public/main.foss.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
											
										
									
								
							
							
								
								
									
										295245
									
								
								public/main.next.dart.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										295245
									
								
								public/main.next.dart.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										21875
									
								
								public/main.profile.dart.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										21875
									
								
								public/main.profile.dart.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -4,6 +4,8 @@ | ||||
|     "/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=803182f668c39d631ca5c55437876da4", | ||||
|     "/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=7bed15f51bca764378d9a3aa605b8664", | ||||
|     "/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=d4f86ddee4e8a1d6e9719010aa0fe62b", | ||||
|     "/js/clients/purchase_orders/action-selectors.js": "/js/clients/purchase_orders/action-selectors.js?id=160b8161599fc2429b449b0970d3ba6c", | ||||
|     "/js/clients/purchase_orders/accept.js": "/js/clients/purchase_orders/accept.js?id=2b5fed3ae34a6fd4db171a77ba72496e", | ||||
|     "/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=b88ad7c8881cc87df07b129c5a7c76df", | ||||
|     "/js/clients/payments/stripe-sofort.js": "/js/clients/payments/stripe-sofort.js?id=1c5493a4c53a5b862d07ee1818179ea9", | ||||
|     "/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=0274ab4f8d2b411f2a2fe5142301e7af", | ||||
| @ -38,7 +40,7 @@ | ||||
|     "/js/clients/payments/stripe-przelewy24.js": "/js/clients/payments/stripe-przelewy24.js?id=3d53d2f7d0291d9f92cf7414dd2d351c", | ||||
|     "/js/clients/payments/stripe-browserpay.js": "/js/clients/payments/stripe-browserpay.js?id=db71055862995fd6ae21becfc587a3de", | ||||
|     "/js/clients/payments/stripe-fpx.js": "/js/clients/payments/stripe-fpx.js?id=914a6846ad1e5584635e7430fef76875", | ||||
|     "/css/app.css": "/css/app.css?id=db0f69e335622f720583392dd572d264", | ||||
|     "/css/app.css": "/css/app.css?id=6419fb85c22d562d4ec14800980801e7", | ||||
|     "/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ada60afcedcb7c", | ||||
|     "/vendor/clipboard.min.js": "/vendor/clipboard.min.js?id=15f52a1ee547f2bdd46e56747332ca2d" | ||||
| } | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								public/react/invoiceninja-logo@dark.365f6449.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/react/invoiceninja-logo@dark.365f6449.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 4.2 KiB | 
							
								
								
									
										103
									
								
								resources/js/clients/purchase_orders/accept.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								resources/js/clients/purchase_orders/accept.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | ||||
| /** | ||||
|  * 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 
 | ||||
|  */ | ||||
| 
 | ||||
| class Accept { | ||||
|     constructor(displaySignature, displayTerms) { | ||||
|         this.shouldDisplaySignature = displaySignature; | ||||
|         this.shouldDisplayTerms = displayTerms; | ||||
|         this.termsAccepted = false; | ||||
|     } | ||||
| 
 | ||||
|     submitForm() { | ||||
|         document.getElementById('approve-form').submit(); | ||||
|     } | ||||
| 
 | ||||
|     displaySignature() { | ||||
|         let displaySignatureModal = document.getElementById( | ||||
|             'displaySignatureModal' | ||||
|         ); | ||||
|         displaySignatureModal.removeAttribute('style'); | ||||
| 
 | ||||
|         const signaturePad = new SignaturePad( | ||||
|             document.getElementById('signature-pad'), | ||||
|             { | ||||
|                 penColor: 'rgb(0, 0, 0)', | ||||
|             } | ||||
|         ); | ||||
| 
 | ||||
|         this.signaturePad = signaturePad; | ||||
|     } | ||||
| 
 | ||||
|     displayTerms() { | ||||
|         let displayTermsModal = document.getElementById("displayTermsModal"); | ||||
|         displayTermsModal.removeAttribute("style"); | ||||
|     } | ||||
| 
 | ||||
|     handle() { | ||||
|         document | ||||
|             .getElementById('approve-button') | ||||
|             .addEventListener('click', () => { | ||||
|                 if (this.shouldDisplaySignature && this.shouldDisplayTerms) { | ||||
|                     this.displaySignature(); | ||||
| 
 | ||||
|                     document | ||||
|                         .getElementById('signature-next-step') | ||||
|                         .addEventListener('click', () => { | ||||
|                             this.displayTerms(); | ||||
| 
 | ||||
|                             document | ||||
|                                 .getElementById('accept-terms-button') | ||||
|                                 .addEventListener('click', () => { | ||||
|                                     document.querySelector( | ||||
|                                         'input[name="signature"' | ||||
|                                     ).value = this.signaturePad.toDataURL(); | ||||
|                                     this.termsAccepted = true; | ||||
|                                     this.submitForm(); | ||||
|                                 }); | ||||
|                         }); | ||||
|                 } | ||||
| 
 | ||||
|                 if (this.shouldDisplaySignature && !this.shouldDisplayTerms) { | ||||
|                     this.displaySignature(); | ||||
| 
 | ||||
|                     document | ||||
|                         .getElementById('signature-next-step') | ||||
|                         .addEventListener('click', () => { | ||||
|                             document.querySelector( | ||||
|                                 'input[name="signature"' | ||||
|                             ).value = this.signaturePad.toDataURL(); | ||||
|                             this.submitForm(); | ||||
|                         }); | ||||
|                 } | ||||
| 
 | ||||
|                 if (!this.shouldDisplaySignature && this.shouldDisplayTerms) { | ||||
|                     this.displayTerms(); | ||||
| 
 | ||||
|                     document | ||||
|                         .getElementById('accept-terms-button') | ||||
|                         .addEventListener('click', () => { | ||||
|                             this.termsAccepted = true; | ||||
|                             this.submitForm(); | ||||
|                         }); | ||||
|                 } | ||||
| 
 | ||||
|                 if (!this.shouldDisplaySignature && !this.shouldDisplayTerms) { | ||||
|                     this.submitForm(); | ||||
|                 } | ||||
|             }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const signature = document.querySelector('meta[name="require-purchase_order-signature"]') | ||||
|     .content; | ||||
| 
 | ||||
| const terms = document.querySelector('meta[name="show-purchase_order-terms"]').content; | ||||
| 
 | ||||
| new Accept(Boolean(+signature), Boolean(+terms)).handle(); | ||||
							
								
								
									
										79
									
								
								resources/js/clients/purchase_orders/action-selectors.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								resources/js/clients/purchase_orders/action-selectors.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | ||||
| /** | ||||
|  * 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 
 | ||||
|  */ | ||||
| 
 | ||||
| class ActionSelectors { | ||||
|     constructor() { | ||||
|         this.parentElement = document.querySelector('.form-check-parent'); | ||||
|         this.parentForm = document.getElementById('bulkActions'); | ||||
|     } | ||||
| 
 | ||||
|     watchCheckboxes(parentElement) { | ||||
|         document | ||||
|             .querySelectorAll('.child-hidden-input') | ||||
|             .forEach((element) => element.remove()); | ||||
| 
 | ||||
|         document.querySelectorAll('.form-check-child').forEach((child) => { | ||||
|             if (parentElement.checked) { | ||||
|                 child.checked = parentElement.checked; | ||||
|                 this.processChildItem( | ||||
|                     child, | ||||
|                     document.getElementById('bulkActions') | ||||
|                 ); | ||||
|             } else { | ||||
|                 child.checked = false; | ||||
|                 document | ||||
|                     .querySelectorAll('.child-hidden-input') | ||||
|                     .forEach((element) => element.remove()); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     processChildItem(element, parent, options = {}) { | ||||
|         if (options.hasOwnProperty('single')) { | ||||
|             document | ||||
|                 .querySelectorAll('.child-hidden-input') | ||||
|                 .forEach((element) => element.remove()); | ||||
|         } | ||||
| 
 | ||||
|         if (element.checked === false) { | ||||
|             let inputs = document.querySelectorAll('input.child-hidden-input'); | ||||
| 
 | ||||
|             for (let i of inputs) { | ||||
|                 if (i.value == element.dataset.value) i.remove(); | ||||
|             } | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         let _temp = document.createElement('INPUT'); | ||||
| 
 | ||||
|         _temp.setAttribute('name', 'purchase_orders[]'); | ||||
|         _temp.setAttribute('value', element.dataset.value); | ||||
|         _temp.setAttribute('class', 'child-hidden-input'); | ||||
|         _temp.hidden = true; | ||||
| 
 | ||||
|         parent.append(_temp); | ||||
|     } | ||||
| 
 | ||||
|     handle() { | ||||
|         this.parentElement.addEventListener('click', () => { | ||||
|             this.watchCheckboxes(this.parentElement); | ||||
|         }); | ||||
| 
 | ||||
|         for (let child of document.querySelectorAll('.form-check-child')) { | ||||
|             child.addEventListener('click', () => { | ||||
|                 this.processChildItem(child, this.parentForm); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** @handle **/ | ||||
| new ActionSelectors().handle(); | ||||
| @ -4623,7 +4623,16 @@ $LANG = array( | ||||
|     'purchase_order_message' => 'To view your purchase order for :amount, click the link below.', | ||||
|     'view_purchase_order' => 'View Purchase Order', | ||||
|     'purchase_orders_backup_subject' => 'Your purchase orders are ready for download', | ||||
| 
 | ||||
|     'notification_purchase_order_viewed_subject' => 'Purchase Order :invoice was viewed by :client', | ||||
|     'notification_purchase_order_viewed' => 'The following vendor :client viewed Purchase Order :invoice for :amount.', | ||||
|     'purchase_order_date' => 'Purchase Order Date', | ||||
|     'purchase_orders' => 'Purchase Orders', | ||||
|     'purchase_order_number_placeholder' => 'Purchase Order # :purchase_order', | ||||
|     'accepted' => 'Accepted', | ||||
|     'activity_137' => ':contact accepted purchase order :purchase_order', | ||||
|     'vendor_information' => 'Vendor Information', | ||||
|     'notification_purchase_order_accepted_subject' => 'Purchase Order :purchase_order was accepted by :vendor', | ||||
|     'notification_purchase_order_accepted' => 'The following vendor :vendor accepted Purchase Order :purchase_order for :amount.', | ||||
| ); | ||||
| 
 | ||||
| return $LANG; | ||||
|  | ||||
							
								
								
									
										2
									
								
								resources/sass/components/badge.scss
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								resources/sass/components/badge.scss
									
									
									
									
										vendored
									
									
								
							| @ -23,7 +23,7 @@ | ||||
| } | ||||
| 
 | ||||
| .badge-warning { | ||||
|     @apply bg-yellow-500 text-yellow-500; | ||||
|     @apply bg-yellow-100 text-yellow-600; | ||||
| } | ||||
| 
 | ||||
| .badge-info { | ||||
|  | ||||
| @ -0,0 +1,76 @@ | ||||
| <div class="hidden md:flex md:flex-shrink-0"> | ||||
|     <div class="flex flex-col w-64"> | ||||
|         <div class="flex items-center h-16 flex-shrink-0 px-4 bg-white border-r justify-center z-10"> | ||||
|             <a href="{{ route('vendor.dashboard') }}"> | ||||
|                 <img class="h-10 w-auto" src="{!! auth()->guard('vendor')->user()->company->present()->logo($settings) !!}" | ||||
|                      alt="{{ auth()->guard('vendor')->user()->company->present()->name() }} logo"/> | ||||
|             </a> | ||||
|         </div> | ||||
|         <div class="h-0 flex-1 flex flex-col overflow-y-auto z-0 border-r"> | ||||
|             <nav class="flex-1 pb-4 pt-0 bg-white"> | ||||
|                 @foreach($sidebar as $row) | ||||
|                     <a class="group flex items-center p-4 text-sm leading-5 font-medium hover:font-semibold focus:outline-none focus:bg-primary-darken transition ease-in-out duration-150 {{ isActive($row['url'], true) ? 'bg-primary text-white' : 'text-gray-900' }}" | ||||
|                        href="{{ route($row['url']) }}"> | ||||
|                         @if(isActive($row['url'], true)) | ||||
|                             <img src="{{ asset('images/svg/' . $row['icon'] . '.svg') }}" | ||||
|                                  class="w-5 h-5 fill-current text-white mr-3" alt=""/> | ||||
|                         @else | ||||
|                             <img src="{{ asset('images/svg/dark/' . $row['icon'] . '.svg') }}" | ||||
|                                  class="w-5 h-5 fill-current text-white mr-3" alt=""/> | ||||
|                         @endif | ||||
| 
 | ||||
|                         <span>{{ $row['title'] }}</span> | ||||
|                     </a> | ||||
|                 @endforeach | ||||
|             </nav> | ||||
| 
 | ||||
|             @if(!auth()->guard('vendor')->user()->user->account->isPaid()) | ||||
|                 <div class="flex-shrink-0 flex bg-white p-4 justify-center"> | ||||
|                     <div class="flex items-center"> | ||||
|                         <a target="_blank" href="https://www.facebook.com/invoiceninja/"> | ||||
|                             <svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg" | ||||
|                                  width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" | ||||
|                                  stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|                                 <path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path> | ||||
|                             </svg> | ||||
|                         </a> | ||||
|                         <a target="_blank" href="https://twitter.com/invoiceninja"> | ||||
|                             <svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg" | ||||
|                                  width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" | ||||
|                                  stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|                                 <path | ||||
|                                     d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path> | ||||
|                             </svg> | ||||
|                         </a> | ||||
|                         <a target="_blank" href="https://github.com/invoiceninja/invoiceninja"> | ||||
|                             <svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg" | ||||
|                                  width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" | ||||
|                                  stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|                                 <path | ||||
|                                     d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path> | ||||
|                             </svg> | ||||
|                         </a> | ||||
|                         <a target="_blank" href="https://www.invoiceninja.com/contact"> | ||||
|                             <svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg" | ||||
|                                  width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" | ||||
|                                  stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|                                 <path | ||||
|                                     d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path> | ||||
|                                 <polyline points="22,6 12,13 2,6"></polyline> | ||||
|                             </svg> | ||||
|                         </a> | ||||
|                         <a target="_blank" href="https://www.youtube.com/channel/UCXAHcBvhW05PDtWYIq7WDFA"> | ||||
|                             <svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg" | ||||
|                                  width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" | ||||
|                                  stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|                                 <path | ||||
|                                     d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"></path> | ||||
|                             </svg> | ||||
|                         </a> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             @endif | ||||
|         </div> | ||||
|         <div class="flex-shrink-0 w-14"></div> | ||||
|     </div> | ||||
| </div> | ||||
| @ -0,0 +1,42 @@ | ||||
| <div class="relative z-10 flex-shrink-0 flex h-16 bg-white shadow" xmlns:x-transition="http://www.w3.org/1999/xhtml"> | ||||
|     <button @click.stop="sidebarOpen = true" class="px-4 border-r border-gray-200 text-gray-500 focus:outline-none focus:bg-gray-100 focus:text-gray-600 md:hidden"> | ||||
|         <svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24"> | ||||
|             <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" /> | ||||
|         </svg> | ||||
|     </button> | ||||
|     <div class="flex-1 px-3 md:px-8 flex justify-between items-center"> | ||||
|         <span class="text-xl text-gray-900" data-ref="meta-title">@yield('meta_title')</span> | ||||
|         <div class="flex items-center md:ml-6 md:mr-2"> | ||||
|              | ||||
|             <div @click.away="open = false" class="ml-3 relative" x-data="{ open: false }"> | ||||
|                 <div> | ||||
|                     <button data-ref="client-profile-dropdown" @click="open = !open" | ||||
|                             class="max-w-xs flex items-center text-sm rounded-full focus:outline-none focus:ring"> | ||||
|                         <img class="h-8 w-8 rounded-full" src="{{ auth()->guard('vendor')->user()->avatar() }}" alt=""/> | ||||
|                         <span class="ml-2 hidden sm:block">{{ auth()->guard('vendor')->user()->present()->name() }}</span> | ||||
|                     </button> | ||||
|                 </div> | ||||
|                 <div x-show="open" style="display:none;" x-transition:enter="transition ease-out duration-100" | ||||
|                      x-transition:enter-start="transform opacity-0 scale-95" | ||||
|                      x-transition:enter-end="transform opacity-100 scale-100" | ||||
|                      x-transition:leave="transition ease-in duration-75" | ||||
|                      x-transition:leave-start="transform opacity-100 scale-100" | ||||
|                      x-transition:leave-end="transform opacity-0 scale-95" | ||||
|                      class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg"> | ||||
|                     <div class="py-1 rounded-md bg-white ring-1 ring-black ring-opacity-5"> | ||||
|                         <a data-ref="client-profile-dropdown-settings" | ||||
|                            href="{{ route('vendor.profile.edit', ['vendor_contact' => auth()->guard('vendor')->user()->hashed_id]) }}" | ||||
|                            class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150"> | ||||
|                             {{ ctrans('texts.profile') }} | ||||
|                         </a> | ||||
| 
 | ||||
|                         <a href="{{ route('vendor.logout') }}" | ||||
|                            class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150"> | ||||
|                             {{ ctrans('texts.logout') }} | ||||
|                         </a> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| @ -0,0 +1,45 @@ | ||||
| <div | ||||
|     class="main_layout h-screen flex overflow-hidden bg-gray-100" | ||||
|     x-data="{ sidebarOpen: false }" | ||||
|     @keydown.window.escape="sidebarOpen = false" | ||||
|     id="main-sidebar"> | ||||
| 
 | ||||
|     @if($settings && $settings->enable_client_portal) | ||||
|         <!-- Off-canvas menu for mobile --> | ||||
|         @include('portal.ninja2020.components.general.sidebar.vendor_mobile', ['sidebar' => $sidebar]) | ||||
| 
 | ||||
|         <!-- Static sidebar for desktop --> | ||||
|         @unless(request()->query('sidebar') === 'hidden') | ||||
|             @include('portal.ninja2020.components.general.sidebar.vendor_desktop', ['sidebar' => $sidebar]) | ||||
|         @endunless | ||||
|     @endif | ||||
| 
 | ||||
|     <div class="flex flex-col w-0 flex-1 overflow-hidden"> | ||||
|         @if($settings && $settings->enable_client_portal) | ||||
|             @include('portal.ninja2020.components.general.sidebar.vendor_header', ['sidebar' => $sidebar]) | ||||
|         @endif | ||||
|          | ||||
|         <main | ||||
|             class="flex-1 relative z-0 overflow-y-auto pt-6 focus:outline-none" | ||||
|             tabindex="0" x-data | ||||
|             x-init="$el.focus()"> | ||||
| 
 | ||||
|             <div class="mx-auto px-4 sm:px-6 md:px-8"> | ||||
|                 @yield('header') | ||||
|             </div> | ||||
| 
 | ||||
|             <div class="mx-auto px-4 sm:px-6 md:px-8"> | ||||
|                 <div class="pt-4 py-6"> | ||||
|                     @includeWhen(session()->has('success'), 'portal.ninja2020.components.general.messages.success') | ||||
|                      | ||||
|                     {{ $slot }} | ||||
|                 </div> | ||||
|             </div> | ||||
|         </main> | ||||
|         @include('portal.ninja2020.components.general.vendor_footer') | ||||
|     </div> | ||||
| </div> | ||||
| 
 | ||||
| <script> | ||||
| 
 | ||||
| </script> | ||||
| @ -0,0 +1,65 @@ | ||||
| <div class="md:hidden"> | ||||
|     <div @click="sidebarOpen = false" class="fixed inset-0 z-30 bg-gray-600 opacity-0 pointer-events-none transition-opacity ease-linear duration-300" :class="{'opacity-75 pointer-events-auto': sidebarOpen, 'opacity-0 pointer-events-none': !sidebarOpen}"></div> | ||||
|     <div class="fixed inset-y-0 left-0 flex flex-col z-40 max-w-xs w-full pt-5 pb-4 bg-white transform ease-in-out duration-300 -translate-x-full" :class="{'translate-x-0': sidebarOpen, '-translate-x-full': !sidebarOpen}"> | ||||
|         <div class="absolute top-0 right-0 -mr-14 p-1"> | ||||
|             <button x-show="sidebarOpen" @click="sidebarOpen = false" class="flex items-center justify-center h-12 w-12 rounded-full focus:outline-none focus:bg-gray-600"> | ||||
|                 <svg class="h-6 w-6 text-white" stroke="currentColor" fill="none" viewBox="0 0 24 24"> | ||||
|                     <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> | ||||
|                 </svg> | ||||
|             </button> | ||||
|         </div> | ||||
|         <div class="flex-shrink-0 flex items-center px-4"> | ||||
|             <img class="h-8 w-auto" src="{!! auth()->guard('vendor')->user()->company->present()->logo($settings) !!}" alt="{{ auth()->guard('vendor')->user()->company->present()->name() }} logo" /> | ||||
|         </div> | ||||
|         <div class="mt-5 flex-1 h-0 overflow-y-auto"> | ||||
|             <nav class="flex-1 pb-4 pt-0 bg-white"> | ||||
|                 @foreach($sidebar as $row) | ||||
|                 <a class="group flex items-center p-4 text-sm leading-5 font-medium hover:font-semibold focus:outline-none focus:font-semibold transition ease-in-out duration-150 {{ isActive($row['url'], true) ? 'bg-primary text-white' : 'text-gray-900' }}" href="{{ route($row['url']) }}"> | ||||
|                     @if(isActive($row['url'], true)) | ||||
|                         <img src="{{ asset('images/svg/' . $row['icon'] . '.svg') }}" | ||||
|                              class="w-5 h-5 fill-current mr-3" alt=""/> | ||||
|                     @else | ||||
|                         <img src="{{ asset('images/svg/dark/' . $row['icon'] . '.svg') }}" | ||||
|                              class="w-5 h-5 fill-current mr-3" alt=""/> | ||||
|                     @endif | ||||
| 
 | ||||
|                     <span>{{ $row['title'] }}</span> | ||||
|                 </a> | ||||
|                 @endforeach | ||||
|             </nav> | ||||
| 
 | ||||
|             @if(!auth()->guard('vendor')->user()->user->account->isPaid()) | ||||
|             <div class="flex-shrink-0 flex bg-white p-4 justify-center"> | ||||
|                 <div class="flex items-center"> | ||||
|                     <a target="_blank" href="https://www.facebook.com/invoiceninja/"> | ||||
|                         <svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|                             <path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path> | ||||
|                         </svg> | ||||
|                     </a> | ||||
|                     <a target="_blank" href="https://twitter.com/invoiceninja"> | ||||
|                         <svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|                             <path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path> | ||||
|                         </svg> | ||||
|                     </a> | ||||
|                     <a target="_blank" href="https://github.com/invoiceninja/invoiceninja"> | ||||
|                         <svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|                             <path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path> | ||||
|                         </svg> </a> | ||||
|                     <a target="_blank" href="https://www.invoiceninja.com/contact"> | ||||
|                         <svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|                             <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path> | ||||
|                             <polyline points="22,6 12,13 2,6"></polyline> | ||||
|                         </svg> | ||||
|                     </a> | ||||
|                     <a target="_blank" href="https://www.youtube.com/channel/UCXAHcBvhW05PDtWYIq7WDFA"> | ||||
|                         <svg class="text-gray-900 hover:text-gray-300 mr-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|                             <path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"></path> | ||||
|                         </svg> | ||||
|                     </a> | ||||
|                 </div> | ||||
|             </div> | ||||
|             @endif | ||||
|         </div> | ||||
|         <div class="flex-shrink-0 w-14"></div> | ||||
|     </div> | ||||
| </div> | ||||
| @ -0,0 +1,48 @@ | ||||
| <footer class="bg-white px-4 py-5 shadow px-4 sm:px-6 md:px-8 flex justify-center border-t border-gray-200 justify-between items-center" x-data="{ privacy: false, tos: false }"> | ||||
|     <section> | ||||
|         @if(auth()->guard('vendor')->user() && auth()->guard('vendor')->user()->user->account->isPaid()) | ||||
|             <span class="text-xs md:text-sm text-gray-700">{{ ctrans('texts.footer_label', ['company' => auth()->guard('vendor')->user()->vendor->company->present()->name(), 'year' => date('Y')]) }}</span> | ||||
|         @else | ||||
|             <span href="https://invoiceninja.com" target="_blank" class="text-xs md:text-sm text-gray-700"> | ||||
|                 {{ ctrans('texts.copyright') }} © {{ date('Y') }} | ||||
|                 <a class="text-primary hover:underline" href="https://invoiceninja.com" target="_blank">Invoice Ninja</a>. | ||||
|             </span> | ||||
|         @endif | ||||
| 
 | ||||
|         <div class="flex items-center"> | ||||
|             @if(strlen($settings->client_portal_privacy_policy) > 1) | ||||
|                 <a x-on:click="privacy = true; tos = false" href="#" class="hover:underline text-sm primary-color flex items-center mr-2">{{ __('texts.privacy_policy')}}</a> | ||||
|             @endif | ||||
| 
 | ||||
|             @if(strlen($settings->client_portal_privacy_policy) > 1 && strlen($settings->client_portal_terms) > 1) | ||||
|                 <!-- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-minus"> | ||||
|                     <line x1="5" y1="12" x2="19" y2="12"></line> | ||||
|                 </svg> Long dash between items. --> | ||||
|             @endif | ||||
| 
 | ||||
|             @if(strlen($settings->client_portal_terms) > 1) | ||||
|                 <a x-on:click="privacy = false; tos = true" href="#" class="hover:underline text-sm primary-color flex items-center mr-2">{{ __('texts.terms')}}</a> | ||||
|             @endif | ||||
|         </div> | ||||
|     </section> | ||||
| 
 | ||||
|     @if(auth()->guard('vendor')->user()->user && !auth()->guard('vendor')->user()->user->account->isPaid()) | ||||
|         <a href="https://invoiceninja.com" target="_blank"> | ||||
|             <img class="h-8" src="{{ asset('images/invoiceninja-black-logo-2.png') }}" alt="Invoice Ninja Logo"> | ||||
|         </a> | ||||
|     @endif | ||||
| 
 | ||||
|     @if(strlen($settings->client_portal_privacy_policy) > 1) | ||||
|         @component('portal.ninja2020.components.general.pop-up', ['title' => __('texts.privacy_policy') ,'show_property' => 'privacy']) | ||||
|             {!! $settings->client_portal_privacy_policy !!} | ||||
|         @endcomponent | ||||
|     @endif | ||||
| 
 | ||||
|     @if(strlen($settings->client_portal_terms) > 1) | ||||
|         @component('portal.ninja2020.components.general.pop-up', ['title' => __('texts.terms') ,'show_property' => 'tos']) | ||||
|             {!! $settings->client_portal_terms !!} | ||||
|         @endcomponent | ||||
|     @endif | ||||
| 
 | ||||
|     <div class="bg-gray-200 hidden"></div> | ||||
| </footer> | ||||
| @ -0,0 +1,121 @@ | ||||
| <div> | ||||
|     <div class="flex items-center justify-between"> | ||||
|         <div class="flex items-center"> | ||||
|             <span class="hidden mr-2 text-sm md:block">{{ ctrans('texts.per_page') }}</span> | ||||
|             <select wire:model="per_page" class="py-1 text-sm form-select"> | ||||
|                 <option>5</option> | ||||
|                 <option selected>10</option> | ||||
|                 <option>15</option> | ||||
|                 <option>20</option> | ||||
|             </select> | ||||
|         </div> | ||||
|         <div class="flex items-center"> | ||||
|             <div class="mr-3"> | ||||
|                 <input wire:model="status" value="sent" type="checkbox" class="cursor-pointer form-checkbox" id="paid-checkbox"> | ||||
|                 <label for="paid-checkbox" class="text-sm cursor-pointer">{{ ctrans('texts.status_sent') }}</label> | ||||
|             </div> | ||||
|             <div class="mr-3"> | ||||
|                 <input wire:model="status" value="accepted" type="checkbox" class="cursor-pointer form-checkbox" id="unpaid-checkbox"> | ||||
|                 <label for="unpaid-checkbox" class="text-sm cursor-pointer">{{ ctrans('texts.accepted') }}</label> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|     <div class="py-2 -my-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8"> | ||||
|         <div class="inline-block min-w-full overflow-hidden align-middle rounded"> | ||||
|             <table class="min-w-full mt-4 border border-gray-200 rounded shadow invoices-table"> | ||||
|                 <thead> | ||||
|                     <tr> | ||||
|                         <th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary"> | ||||
|                             <label> | ||||
|                                 <input type="checkbox" class="form-check form-check-parent"> | ||||
|                             </label> | ||||
|                         </th> | ||||
|                         <th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary"> | ||||
|                             <span role="button" wire:click="sortBy('number')" class="cursor-pointer"> | ||||
|                                 {{ ctrans('texts.purchase_order_number_short') }} | ||||
|                             </span> | ||||
|                         </th> | ||||
|                         <th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary"> | ||||
|                             <span role="button" wire:click="sortBy('date')" class="cursor-pointer"> | ||||
|                                 {{ ctrans('texts.purchase_order_date') }} | ||||
|                             </span> | ||||
|                         </th> | ||||
|                         <th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary"> | ||||
|                             <span role="button" wire:click="sortBy('amount')" class="cursor-pointer"> | ||||
|                                 {{ ctrans('texts.amount') }} | ||||
|                             </span> | ||||
|                         </th> | ||||
|                         <th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary"> | ||||
|                             <span role="button" wire:click="sortBy('balance')" class="cursor-pointer"> | ||||
|                                 {{ ctrans('texts.balance') }} | ||||
|                             </span> | ||||
|                         </th> | ||||
|                         <th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary"> | ||||
|                             <span role="button" wire:click="sortBy('due_date')" class="cursor-pointer"> | ||||
|                                 {{ ctrans('texts.due_date') }} | ||||
|                             </span> | ||||
|                         </th> | ||||
|                         <th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary"> | ||||
|                             <span role="button" wire:click="sortBy('status_id')" class="cursor-pointer"> | ||||
|                                 {{ ctrans('texts.status') }} | ||||
|                             </span> | ||||
|                         </th> | ||||
|                         <th class="px-white-3 border-b border-gray-200 bg-primary"></th> | ||||
|                     </tr> | ||||
|                 </thead> | ||||
|                 <tbody> | ||||
|                     @forelse($purchase_orders as $purchase_order) | ||||
|                         <tr class="bg-white group hover:bg-gray-100"> | ||||
|                             <td class="px-6 py-4 text-sm font-medium leading-5 text-gray-900 whitespace-nowrap"> | ||||
|                                 <label> | ||||
|                                     <input type="checkbox" class="form-check form-check-child" data-value="{{ $purchase_order->hashed_id }}"> | ||||
|                                 </label> | ||||
|                             </td> | ||||
|                             <td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-nowrap"> | ||||
|                                 {{ $purchase_order->number }} | ||||
|                             </td> | ||||
|                             <td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-nowrap"> | ||||
|                                 {{ $purchase_order->translateDate($purchase_order->date, $purchase_order->company->date_format(), $purchase_order->company->locale()) }} | ||||
|                             </td> | ||||
|                             <td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-nowrap"> | ||||
|                                 {{ App\Utils\Number::formatMoney($purchase_order->amount, $purchase_order->company) }} | ||||
|                             </td> | ||||
|                             <td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-nowrap"> | ||||
|                             {{ App\Utils\Number::formatMoney($purchase_order->balance, $purchase_order->company) }} | ||||
|                             </td> | ||||
|                             <td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-nowrap"> | ||||
|                                 {{ $purchase_order->translateDate($purchase_order->due_date, $purchase_order->company->date_format(), $purchase_order->company->locale()) }} | ||||
|                             </td> | ||||
|                             <td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-nowrap"> | ||||
|                                 {!! App\Models\PurchaseOrder::badgeForStatus($purchase_order->status_id) !!} | ||||
|                             </td> | ||||
|                             <td class="flex items-center justify-end px-6 py-4 text-sm font-medium leading-5 whitespace-nowrap"> | ||||
|                                 <a href="{{ route('vendor.purchase_order.show', $purchase_order->hashed_id) }}" class="button-link text-primary"> | ||||
|                                     {{ ctrans('texts.view') }} | ||||
|                                 </a> | ||||
|                             </td> | ||||
|                         </tr> | ||||
|                     @empty | ||||
|                         <tr class="bg-white group hover:bg-gray-100"> | ||||
|                             <td class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-nowrap" colspan="100%"> | ||||
|                                 {{ ctrans('texts.no_results') }} | ||||
|                             </td> | ||||
|                         </tr> | ||||
|                     @endforelse | ||||
|                 </tbody> | ||||
|             </table> | ||||
|         </div> | ||||
|     </div> | ||||
|     <div class="flex justify-center mt-6 mb-6 md:justify-between"> | ||||
|         @if($purchase_orders && $purchase_orders->total() > 0) | ||||
|             <span class="hidden text-sm text-gray-700 md:block mr-2"> | ||||
|                 {{ ctrans('texts.showing_x_of', ['first' => $purchase_orders->firstItem(), 'last' => $purchase_orders->lastItem(), 'total' => $purchase_orders->total()]) }} | ||||
|             </span> | ||||
|         @endif | ||||
|         {{ $purchase_orders->links('portal/ninja2020/vendor/pagination') }} | ||||
|     </div> | ||||
| </div> | ||||
| 
 | ||||
| @push('footer') | ||||
|     <script src="{{ asset('js/clients/purchase_orders/action-selectors.js') }}"></script> | ||||
| @endpush | ||||
							
								
								
									
										168
									
								
								resources/views/portal/ninja2020/layout/vendor_app.blade.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								resources/views/portal/ninja2020/layout/vendor_app.blade.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,168 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> | ||||
| 
 | ||||
|     <head> | ||||
|         <!-- Error: {{ session('error') }} --> | ||||
| 
 | ||||
|         @if (config('services.analytics.tracking_id')) | ||||
|             <script async src="https://www.googletagmanager.com/gtag/js?id=UA-122229484-1"></script> | ||||
|             <script> | ||||
|                 window.dataLayer = window.dataLayer || []; | ||||
| 
 | ||||
|                 function gtag() { | ||||
|                     dataLayer.push(arguments); | ||||
|                 } | ||||
| 
 | ||||
|                 gtag('js', new Date()); | ||||
|                 gtag('config', '{{ config('services.analytics.tracking_id') }}', {'anonymize_ip': true}); | ||||
| 
 | ||||
|                 function trackEvent(category, action) { | ||||
|                     ga('send', 'event', category, action, this.src); | ||||
|                 } | ||||
|             </script> | ||||
|             <script> | ||||
|                 Vue.config.devtools = true; | ||||
|             </script> | ||||
|         @else | ||||
|             <script> | ||||
|                 function gtag() { | ||||
|                 } | ||||
|             </script> | ||||
|         @endif | ||||
| 
 | ||||
|         <!-- Title --> | ||||
|         @if(isset($company->account) && !$company->account->isPaid()) | ||||
|             <title>@yield('meta_title', '') — Invoice Ninja</title> | ||||
|         @elseif(isset($company) && !is_null($company)) | ||||
|             <title>@yield('meta_title', '') — {{ $company->present()->name() }}</title> | ||||
|         @else | ||||
|             <title>@yield('meta_title', '')</title> | ||||
|         @endif | ||||
| 
 | ||||
|         <meta charset="utf-8"> | ||||
|         <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|         <meta name="description" content="@yield('meta_description')"/> | ||||
|          | ||||
|         <!-- CSRF Token --> | ||||
|         <meta name="csrf-token" content="{{ csrf_token() }}"> | ||||
| 
 | ||||
|         <!-- Scripts --> | ||||
|         <script src="{{ mix('js/app.js') }}" defer></script> | ||||
|         <script src="{{ asset('vendor/alpinejs@2.8.2/alpine.js') }}" defer></script> | ||||
| 
 | ||||
|         <!-- Fonts --> | ||||
|         <link rel="dns-prefetch" href="https://fonts.gstatic.com"> | ||||
|         <link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet" type="text/css"> | ||||
| 
 | ||||
|         <!-- Styles --> | ||||
|         <link href="{{ mix('css/app.css') }}" rel="stylesheet"> | ||||
| 
 | ||||
|         @if(auth()->guard('vendor')->user() && !auth()->guard('vendor')->user()->user->account->isPaid()) | ||||
|             <link href="{{ asset('favicon.png') }}" rel="shortcut icon" type="image/png"> | ||||
|         @endif | ||||
| 
 | ||||
|         <link rel="canonical" href="{{ config('ninja.site_url') }}/{{ request()->path() }}"/> | ||||
| 
 | ||||
|         @if((bool) \App\Utils\Ninja::isSelfHost()) | ||||
|             <style> | ||||
|                 {!! $settings->portal_custom_css !!} | ||||
|             </style> | ||||
|         @endif | ||||
| 
 | ||||
|         @livewireStyles | ||||
| 
 | ||||
|         {{-- Feel free to push anything to header using @push('header') --}} | ||||
|         @stack('head') | ||||
| 
 | ||||
|         @if((isset($company) && $company->account->isPaid() && !empty($settings->portal_custom_head)) || ((bool) \App\Utils\Ninja::isSelfHost() && !empty($settings->portal_custom_head))) | ||||
|             <div class="py-1 text-sm text-center text-white bg-primary"> | ||||
|                 {!! $settings->portal_custom_head !!} | ||||
|             </div> | ||||
|         @endif | ||||
| 
 | ||||
|         <link rel="stylesheet" type="text/css" href="{{ asset('vendor/cookieconsent@3/cookieconsent.min.css') }}" /> | ||||
|     </head> | ||||
| 
 | ||||
|     @include('portal.ninja2020.components.primary-color') | ||||
| 
 | ||||
|     <body class="antialiased"> | ||||
|         @if(session()->has('message')) | ||||
|             <div class="py-1 text-sm text-center text-white bg-primary disposable-alert"> | ||||
|                 {{ session('message') }} | ||||
|             </div> | ||||
|         @endif | ||||
| 
 | ||||
|         @component('portal.ninja2020.components.general.sidebar.vendor_main', ['settings' => $settings, 'sidebar' => $sidebar]) | ||||
|             @yield('body') | ||||
|         @endcomponent | ||||
| 
 | ||||
|         @livewireScripts | ||||
| 
 | ||||
|         <script src="{{ asset('vendor/cookieconsent@3/cookieconsent.min.js') }}" data-cfasync="false"></script> | ||||
|         <script> | ||||
|             window.addEventListener("load", function(){ | ||||
|                 if (! window.cookieconsent) { | ||||
|                     return; | ||||
|                 } | ||||
|                 window.cookieconsent.initialise({ | ||||
|                     "palette": { | ||||
|                         "popup": { | ||||
|                             "background": "#000" | ||||
|                         }, | ||||
|                         "button": { | ||||
|                             "background": "#f1d600" | ||||
|                         }, | ||||
|                     }, | ||||
|                     "content": { | ||||
|                         "href": "{{ config('ninja.privacy_policy_url.hosted') }}", | ||||
|                         "message": "This website uses cookies to ensure you get the best experience on our website.", | ||||
|                         "dismiss": "Got it!", | ||||
|                         "link": "Learn more", | ||||
|                     } | ||||
|                 })} | ||||
|             ); | ||||
|         </script> | ||||
| 
 | ||||
|         @if($company && $company->google_analytics_key) | ||||
|             <script> | ||||
|                 (function (i, s, o, g, r, a, m) { | ||||
|                     i['GoogleAnalyticsObject'] = r; | ||||
|                     i[r] = i[r] || function () { | ||||
|                                 (i[r].q = i[r].q || []).push(arguments) | ||||
|                             }, i[r].l = 1 * new Date(); | ||||
|                     a = s.createElement(o), | ||||
|                             m = s.getElementsByTagName(o)[0]; | ||||
|                     a.async = 1; | ||||
|                     a.src = g; | ||||
|                     m.parentNode.insertBefore(a, m) | ||||
|                 })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga'); | ||||
| 
 | ||||
|                 ga('create', '{{ $company->google_analytics_key }}', 'auto'); | ||||
|                 ga('set', 'anonymizeIp', true); | ||||
|                 ga('send', 'pageview'); | ||||
| 
 | ||||
|                 function trackEvent(category, action) { | ||||
|                     ga('send', 'event', category, action, this.src); | ||||
|                 } | ||||
|             </script> | ||||
|         @endif | ||||
|          | ||||
|     </body> | ||||
| 
 | ||||
|     <footer> | ||||
|         @yield('footer') | ||||
|         @stack('footer') | ||||
| 
 | ||||
|         @if((bool) \App\Utils\Ninja::isSelfHost() && !empty($settings->portal_custom_footer)) | ||||
|             <div class="py-1 text-sm text-center text-white bg-primary"> | ||||
|                 {!! $settings->portal_custom_footer !!} | ||||
|             </div> | ||||
|         @endif | ||||
|     </footer> | ||||
| 
 | ||||
|     @if((bool) \App\Utils\Ninja::isSelfHost()) | ||||
|         <script> | ||||
|             {!! $settings->portal_custom_js !!} | ||||
|         </script> | ||||
|     @endif | ||||
| </html> | ||||
| @ -0,0 +1,11 @@ | ||||
| @extends('portal.ninja2020.layout.clean') | ||||
| @section('meta_title', ctrans('texts.vendor')) | ||||
| 
 | ||||
| @component('portal.ninja2020.components.test') | ||||
| @endcomponent | ||||
| 
 | ||||
| @section('body') | ||||
| <div class="flex justify-center items-center h-screen"> | ||||
|   <h1>Vendor Portal</h1> | ||||
| </div> | ||||
| @endsection | ||||
| @ -0,0 +1,40 @@ | ||||
| <form action="{{ route('vendor.purchase_orders.bulk') }}" method="post" id="approve-form" /> | ||||
| @csrf | ||||
| 
 | ||||
| <input type="hidden" name="action" value="accept"> | ||||
| <input type="hidden" name="process" value="true"> | ||||
| <input type="hidden" name="purchase_orders[]" value="{{ $purchase_order->hashed_id }}"> | ||||
| <input type="hidden" name="signature"> | ||||
| 
 | ||||
| <div class="bg-white shadow sm:rounded-lg"> | ||||
|     <div class="px-4 py-5 sm:p-6"> | ||||
|         <div class="sm:flex sm:items-start sm:justify-between"> | ||||
|             <div> | ||||
|              | ||||
|                 <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||
|                     {{ ctrans('texts.approve') }} | ||||
|                 </h3> | ||||
|                  | ||||
|                 <div class="btn hidden md:block" data-clipboard-text="{{url("vendor/purchase_order/{$key}")}}" aria-label="Copied!"> | ||||
|                     <div class="flex text-sm leading-6 font-medium text-gray-500"> | ||||
|                         <p class="mr-2">{{url("vendor/purchase_order/{$key}")}}</p> | ||||
|                         <p><img class="h-5 w-5" src="{{ asset('assets/clippy.svg') }}" alt="Copy to clipboard"></p> | ||||
|                     </div> | ||||
|                 </div> | ||||
|              | ||||
|             </div> | ||||
| 
 | ||||
|             <div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center"> | ||||
|                 @yield('quote-not-approved-right-side') | ||||
| 
 | ||||
|                 <div class="inline-flex rounded-md shadow-sm"> | ||||
|                     <button onclick="setTimeout(() => this.disabled = true, 0); return true;" type="button" | ||||
|                         class="button button-primary bg-primary" | ||||
|                         id="approve-button">{{ ctrans('texts.accept') }}</button> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| 
 | ||||
| </form> | ||||
| @ -0,0 +1,25 @@ | ||||
| @extends('portal.ninja2020.layout.vendor_app') | ||||
| @section('meta_title', ctrans('texts.purchase_orders')) | ||||
| 
 | ||||
| @section('header') | ||||
|     @if($errors->any()) | ||||
|         <div class="alert alert-failure mb-4"> | ||||
|             @foreach($errors->all() as $error) | ||||
|             <p>{{ $error }}</p> | ||||
|             @endforeach | ||||
|         </div> | ||||
|     @endif | ||||
| @endsection | ||||
| 
 | ||||
| @section('body') | ||||
|     <div class="flex items-center"> | ||||
|         <form action="{{ route('vendor.purchase_orders.bulk') }}" method="post" id="bulkActions"> | ||||
|             @csrf | ||||
|             <button type="submit" onclick="setTimeout(() => this.disabled = true, 0); setTimeout(() => this.disabled = false, 5000); return true;" class="button button-primary bg-primary" name="action" value="download">{{ ctrans('texts.download') }}</button> | ||||
| 
 | ||||
|             </form> | ||||
|     </div> | ||||
|     <div class="flex flex-col mt-4"> | ||||
|         @livewire('purchase-orders-table', ['company' => $company]) | ||||
|     </div> | ||||
| @endsection | ||||
| @ -0,0 +1,59 @@ | ||||
| @extends('portal.ninja2020.layout.vendor_app') | ||||
| @section('meta_title', ctrans('texts.view_purchase_order')) | ||||
| 
 | ||||
| @push('head') | ||||
|     <meta name="show-purchase_order-terms" content="false"> | ||||
|     <meta name="require-purchase_order-signature" content="{{ $purchase_order->company->account->hasFeature(\App\Models\Account::FEATURE_INVOICE_SETTINGS) && $settings->require_purchase_order_signature }}"> | ||||
|     @include('portal.ninja2020.components.no-cache') | ||||
|      | ||||
|     <script src="{{ asset('vendor/signature_pad@2.3.2/signature_pad.min.js') }}"></script> | ||||
| 
 | ||||
| @endpush | ||||
| 
 | ||||
| @section('body') | ||||
| 
 | ||||
|     @if(in_array($purchase_order->status_id, [\App\Models\PurchaseOrder::STATUS_SENT, \App\Models\PurchaseOrder::STATUS_DRAFT])) | ||||
|     <div class="mb-4"> | ||||
|         @include('portal.ninja2020.purchase_orders.includes.actions', ['purchase_order' => $purchase_order]) | ||||
|     </div> | ||||
|     @else | ||||
|         <div class="bg-white shadow sm:rounded-lg mb-4"> | ||||
|             <div class="px-4 py-5 sm:p-6"> | ||||
|                 <div class="sm:flex sm:items-start sm:justify-between"> | ||||
|                     <div> | ||||
|                         <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||
|                             {{ ctrans('texts.purchase_order_number_placeholder', ['purchase_order' => $purchase_order->number])}} | ||||
|                             - {{ \App\Models\PurchaseOrder::stringStatus($purchase_order->status_id) }} | ||||
|                         </h3> | ||||
| 
 | ||||
|                             @if($key) | ||||
|                             <div class="btn hidden md:block" data-clipboard-text="{{url("vendor/purchase_order/{$key}")}}" aria-label="Copied!"> | ||||
|                                 <div class="flex text-sm leading-6 font-medium text-gray-500"> | ||||
|                                     <p class="pr-10">{{url("vendor/purchase_order/{$key}")}}</p> | ||||
|                                     <p><img class="h-5 w-5" src="{{ asset('assets/clippy.svg') }}" alt="Copy to clipboard"></p> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                             @endif | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     @endif | ||||
| 
 | ||||
|     @include('portal.ninja2020.components.entity-documents', ['entity' => $purchase_order]) | ||||
|     @include('portal.ninja2020.components.pdf-viewer', ['entity' => $purchase_order]) | ||||
|     @include('portal.ninja2020.invoices.includes.terms', ['entities' => [$purchase_order], 'entity_type' => ctrans('texts.purchase_order')]) | ||||
|     @include('portal.ninja2020.invoices.includes.signature') | ||||
| @endsection | ||||
| 
 | ||||
| @section('footer') | ||||
|     <script src="{{ asset('js/clients/purchase_orders/accept.js') }}"></script> | ||||
|     <script src="{{ asset('vendor/clipboard.min.js') }}"></script> | ||||
| 
 | ||||
|     <script type="text/javascript"> | ||||
| 
 | ||||
|         var clipboard = new ClipboardJS('.btn'); | ||||
| 
 | ||||
|     </script> | ||||
| @endsection | ||||
| 
 | ||||
							
								
								
									
										177
									
								
								resources/views/portal/ninja2020/vendor_profile/edit.blade.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								resources/views/portal/ninja2020/vendor_profile/edit.blade.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,177 @@ | ||||
| @extends('portal.ninja2020.layout.vendor_app') | ||||
| 
 | ||||
| @section('meta_title', ctrans('texts.vendor_information')) | ||||
| 
 | ||||
| @section('header') | ||||
|     <p class="leading-5 text-gray-500">{{ ctrans('texts.update_your_personal_info') }}</p> | ||||
| @endsection | ||||
| 
 | ||||
| @section('body') | ||||
|     @if(session()->has('missing_required_fields')) | ||||
|         <div class="validation validation-fail"> | ||||
|             <p class="mb-3 font-semibold">{{ ctrans('texts.before_proceeding_with_payment_warning') }}:</p> | ||||
| 
 | ||||
|             <ul> | ||||
|                 @foreach(session()->get('missing_required_fields') as $field) | ||||
|                     <li class="block">— {{ ctrans("texts.{$field}") }}</li> | ||||
|                 @endforeach | ||||
|             </ul> | ||||
| 
 | ||||
|             <button onclick="window.history.back();" class="block mt-3 button button-link pl-0 ml-0 underline">{{ ctrans('texts.after_completing_go_back_to_previous_page') }}</button> | ||||
|         </div> | ||||
|     @endif | ||||
| 
 | ||||
|     <div class="mt-2 sm:mt-6"> | ||||
|         <div class="md:grid md:grid-cols-3 md:gap-6"> | ||||
|             <div class="md:col-span-1"> | ||||
|                 <div class="sm:px-0"> | ||||
|                     <h3 class="text-lg font-medium leading-6 text-gray-900">{{ ctrans('texts.contact_details') }}</h3> | ||||
|                 </div> | ||||
|             </div>  <!-- End of left-side --> | ||||
| 
 | ||||
|             <div class="mt-5 md:mt-0 md:col-span-2"> | ||||
|             	<form action="{{ route('vendor.profile.update', ['vendor_contact' => $contact->hashed_id]) }}" method="post" id="saveVendor"> | ||||
|                     @csrf | ||||
|                     @method('PUT') | ||||
|                     <div class="shadow overflow-hidden rounded"> | ||||
|                         <div class="px-4 py-5 bg-white sm:p-6"> | ||||
|                             <div class="grid grid-cols-6 gap-6"> | ||||
|                                 <div class="col-span-6 sm:col-span-3"> | ||||
|                                     <label for="first_name" class="input-label">@lang('texts.first_name')</label> | ||||
|                                     <input id="contact_first_name" | ||||
|                                            class="input w-full {{ in_array('contact_first_name', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" | ||||
|                                            name="first_name" value="{{ $contact->first_name }}"/> | ||||
|                                     @error('first_name') | ||||
|                                     <div class="validation validation-fail"> | ||||
|                                         {{ $message }} | ||||
|                                     </div> | ||||
|                                     @enderror | ||||
|                                 </div> | ||||
| 
 | ||||
|                                 <div class="col-span-6 sm:col-span-3"> | ||||
|                                     <label for="last_name" class="input-label">@lang('texts.last_name')</label> | ||||
|                                     <input id="contact_last_name" | ||||
|                                            class="input w-full {{ in_array('contact_last_name', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" | ||||
|                                            name="last_name" value="{{ $contact->last_name}}"/> | ||||
|                                     @error('last_name') | ||||
|                                     <div class="validation validation-fail"> | ||||
|                                         {{ $message }} | ||||
|                                     </div> | ||||
|                                     @enderror | ||||
|                                 </div> | ||||
| 
 | ||||
|                                 <div class="col-span-6 sm:col-span-4"> | ||||
|                                     <label for="email_address" class="input-label">@lang('texts.email_address')</label> | ||||
|                                     <input id="contact_email_address" | ||||
|                                            class="input w-full {{ in_array('contact_email', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" | ||||
|                                            type="email" name="email" value="{{ $contact->email }}"/> | ||||
|                                     @error('email') | ||||
|                                     <div class="validation validation-fail"> | ||||
|                                         {{ $message }} | ||||
|                                     </div> | ||||
|                                     @enderror | ||||
|                                 </div> | ||||
| 
 | ||||
|                                 <div class="col-span-6 sm:col-span-4"> | ||||
|                                     <label for="contact_phone" class="input-label">@lang('texts.phone')</label> | ||||
|                                     <input id="contact_phone" class="input w-full" name="phone" | ||||
|                                            value="{{ $contact->phone}}"/> | ||||
|                                     @error('phone') | ||||
|                                     <div class="validation validation-fail"> | ||||
|                                         {{ $message }} | ||||
|                                     </div> | ||||
|                                     @enderror | ||||
|                                 </div> | ||||
| 
 | ||||
| 
 | ||||
|                             </div> | ||||
|                         </div> | ||||
| 
 | ||||
|                     </div> | ||||
|             </div> <!-- End of main form --> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
| <div class="mt-10 sm:mt-6"> | ||||
|     <div class="md:grid md:grid-cols-3 md:gap-6"> | ||||
|         <div class="md:col-span-1"> | ||||
|             <div class="sm:px-0"> | ||||
|                 <h3 class="text-lg font-medium leading-6 text-gray-900">{{ ctrans('texts.billing_address') }}</h3> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="mt-5 md:mt-0 md:col-span-2"> | ||||
|                 <div class="px-4 py-5 bg-white sm:p-6"> | ||||
|                     <div class="grid grid-cols-6 gap-6"> | ||||
|                         <div class="col-span-6 sm:col-span-4"> | ||||
|                             <label for="address1" class="input-label">{{ ctrans('texts.address1') }}</label> | ||||
|                             <input id="address1" class="input w-full {{ in_array('billing_address1', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="address1" value="{{ $vendor->address1 }}" /> | ||||
|                             @error('address1') | ||||
|                             <div class="validation validation-fail"> | ||||
|                                 {{ $message }} | ||||
|                             </div> | ||||
|                             @enderror | ||||
|                         </div> | ||||
|                         <div class="col-span-6 sm:col-span-3"> | ||||
|                             <label for="address2" class="input-label">{{ ctrans('texts.address2') }}</label> | ||||
|                             <input id="address2" class="input w-full {{ in_array('billing_address2', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="address2" value="{{ $vendor->address2 }}" /> | ||||
|                             @error('address2') | ||||
|                             <div class="validation validation-fail"> | ||||
|                                 {{ $message }} | ||||
|                             </div> | ||||
|                             @enderror | ||||
|                         </div> | ||||
|                         <div class="col-span-6 sm:col-span-3"> | ||||
|                             <label for="city" class="input-label">{{ ctrans('texts.city') }}</label> | ||||
|                             <input id="city" class="input w-full {{ in_array('billing_city', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="city" value="{{ $vendor->city }}" /> | ||||
|                             @error('city') | ||||
|                             <div class="validation validation-fail"> | ||||
|                                 {{ $message }} | ||||
|                             </div> | ||||
|                             @enderror | ||||
|                         </div> | ||||
|                         <div class="col-span-6 sm:col-span-2"> | ||||
|                             <label for="state" class="input-label">{{ ctrans('texts.state') }}</label> | ||||
|                             <input id="state" class="input w-full {{ in_array('billing_state', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="state" value="{{ $vendor->state }}" /> | ||||
|                             @error('state') | ||||
|                             <div class="validation validation-fail"> | ||||
|                                 {{ $message }} | ||||
|                             </div> | ||||
|                             @enderror | ||||
|                         </div> | ||||
|                         <div class="col-span-6 sm:col-span-2"> | ||||
|                             <label for="postal_code" class="input-label">{{ ctrans('texts.postal_code') }}</label> | ||||
|                             <input id="postal_code" class="input w-full {{ in_array('billing_postal_code', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="postal_code" value="{{ $vendor->postal_code }}" /> | ||||
|                             @error('postal_code') | ||||
|                             <div class="validation validation-fail"> | ||||
|                                 {{ $message }} | ||||
|                             </div> | ||||
|                             @enderror | ||||
|                         </div> | ||||
|                         <div class="col-span-6 sm:col-span-2"> | ||||
|                             <label for="country" class="input-label">@lang('texts.country')</label> | ||||
|                             <select id="country" class="input w-full bg-white form-select {{ in_array('billing_country', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" value="{{ $vendor->country_id }}" name="country_id"> | ||||
|                                 <option value="none"></option> | ||||
|                                 @foreach($countries as $country) | ||||
|                                     <option value="{{ $country->id }}" @if($vendor->country_id == $country->id) selected @endif> | ||||
|                                         {{ $country->iso_3166_2 }} ({{ $country->name }}) | ||||
|                                     </option> | ||||
|                                 @endforeach | ||||
|                             </select> | ||||
|                             @error('country') | ||||
|                             <div class="validation validation-fail"> | ||||
|                                 {{ $message }} | ||||
|                             </div> | ||||
|                             @enderror | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div class="px-4 py-3 bg-gray-50 text-right sm:px-6"> | ||||
|                     <button type="submit" class="button button-primary bg-primary">{{ ctrans('texts.save') }}</button> | ||||
|                 </div> | ||||
|         </div> | ||||
|         </form> | ||||
|     </div> | ||||
| </div> | ||||
| 
 | ||||
| 
 | ||||
| @endsection | ||||
| @ -17,4 +17,15 @@ | ||||
|    | ||||
| </body> | ||||
| 
 | ||||
| <!-- | ||||
| 
 | ||||
| If you are reading this, there is a fair change that the react application has not loaded for you. There are a couple of solutions: | ||||
| 
 | ||||
| 1. Download the release file from https://github.com/invoiceninja/invoiceninja and overwrite your current installation. | ||||
| 2. Switch back to the Flutter application by editing the database, you can do this with the following SQL | ||||
| 
 | ||||
| UPDATE accounts SET | ||||
| set_react_as_default_ap = 0; | ||||
| 
 | ||||
| --> | ||||
| </html> | ||||
|  | ||||
| @ -24,6 +24,7 @@ Route::group(['middleware' => ['throttle:10,1','api_secret_check','email_db']], | ||||
| }); | ||||
| 
 | ||||
| Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale'], 'prefix' => 'api/v1', 'as' => 'api.'], function () { | ||||
|     Route::put('accounts/{account}', 'AccountController@update')->name('account.update'); | ||||
|     Route::post('check_subdomain', 'SubdomainController@index')->name('check_subdomain'); | ||||
|     Route::get('ping', 'PingController@index')->name('ping'); | ||||
|     Route::get('health_check', 'PingController@health')->name('health_check'); | ||||
|  | ||||
| @ -9,5 +9,35 @@ | ||||
| | is assigned the "api" middleware group. Enjoy building your API! | ||||
| | | ||||
| */ | ||||
| use App\Http\Controllers\Auth\VendorContactLoginController; | ||||
| use App\Http\Controllers\VendorPortal\InvitationController; | ||||
| use App\Http\Controllers\VendorPortal\PurchaseOrderController; | ||||
| use App\Http\Controllers\VendorPortal\VendorContactController; | ||||
| use Illuminate\Support\Facades\Route; | ||||
| 
 | ||||
| Route::get('vendors', [VendorContactLoginController::class, 'catch'])->name('vendor.catchall')->middleware(['domain_db', 'contact_account','vendor_locale']); //catch all
 | ||||
| 
 | ||||
| Route::group(['middleware' => ['invite_db'], 'prefix' => 'vendor', 'as' => 'vendor.'], function () { | ||||
|     /*Invitation catches*/ | ||||
|     Route::get('purchase_order/{invitation_key}', [InvitationController::class, 'purchaseOrder']); | ||||
|  //   Route::get('purchase_order/{invitation_key}/download_pdf', 'PurchaseOrderController@downloadPdf')->name('recurring_invoice.download_invitation_key');
 | ||||
|  //   Route::get('purchase_order/{invitation_key}/download', 'ClientPortal\InvitationController@routerForDownload');
 | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| 
 | ||||
| Route::group(['middleware' => ['auth:vendor', 'vendor_locale', 'domain_db'], 'prefix' => 'vendor', 'as' => 'vendor.'], function () { | ||||
| 
 | ||||
|     Route::get('dashboard', [PurchaseOrderController::class, 'index'])->name('dashboard'); | ||||
|     Route::get('purchase_orders', [PurchaseOrderController::class, 'index'])->name('purchase_orders.index'); | ||||
|     Route::get('purchase_orders/{purchase_order}', [PurchaseOrderController::class, 'show'])->name('purchase_order.show'); | ||||
| 
 | ||||
|     Route::get('profile/{vendor_contact}/edit', [VendorContactController::class, 'edit'])->name('profile.edit'); | ||||
|     Route::put('profile/{vendor_contact}/edit', [VendorContactController::class, 'update'])->name('profile.update'); | ||||
| 
 | ||||
|     Route::post('purchase_orders/bulk', [PurchaseOrderController::class, 'bulk'])->name('purchase_orders.bulk'); | ||||
|     Route::get('logout', [VendorContactLoginController::class, 'logout'])->name('logout'); | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| Route::fallback('BaseController@notFoundVendor'); | ||||
| @ -85,27 +85,27 @@ class InventoryManagementTest extends TestCase | ||||
|         $this->assertEquals(90, $product->in_stock_quantity); | ||||
| 
 | ||||
| 
 | ||||
|         $arr = $response->json(); | ||||
|         $invoice_hashed_id = $arr['data']['id']; | ||||
|         // $arr = $response->json();
 | ||||
|         // $invoice_hashed_id = $arr['data']['id'];
 | ||||
| 
 | ||||
|         $invoice_item = new InvoiceItem; | ||||
|         $invoice_item->type_id = 1; | ||||
|         $invoice_item->product_key = $product->product_key; | ||||
|         $invoice_item->notes = $product->notes; | ||||
|         $invoice_item->quantity = 5; | ||||
|         $invoice_item->cost = 100; | ||||
|         // $invoice_item = new InvoiceItem;
 | ||||
|         // $invoice_item->type_id = 1;
 | ||||
|         // $invoice_item->product_key = $product->product_key;
 | ||||
|         // $invoice_item->notes = $product->notes;
 | ||||
|         // $invoice_item->quantity = 5;
 | ||||
|         // $invoice_item->cost = 100;
 | ||||
| 
 | ||||
|         $line_items2[] = $invoice_item; | ||||
|         $invoice->line_items = $line_items2; | ||||
|         // $line_items2[] = $invoice_item;
 | ||||
|         // $invoice->line_items = $line_items2;
 | ||||
| 
 | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->put('/api/v1/invoices/'.$invoice_hashed_id, $invoice->toArray()) | ||||
|         ->assertStatus(200); | ||||
|         // $response = $this->withHeaders([
 | ||||
|         //     'X-API-SECRET' => config('ninja.api_secret'),
 | ||||
|         //     'X-API-TOKEN' => $this->token,
 | ||||
|         // ])->put('/api/v1/invoices/'.$invoice_hashed_id, $invoice->toArray())
 | ||||
|         // ->assertStatus(200);
 | ||||
| 
 | ||||
|         $product = $product->refresh(); | ||||
|         // $product = $product->refresh();
 | ||||
| 
 | ||||
|         $this->assertEquals(95, $product->in_stock_quantity); | ||||
|         // $this->assertEquals(95, $product->in_stock_quantity);
 | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										8
									
								
								webpack.mix.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								webpack.mix.js
									
									
									
									
										vendored
									
									
								
							| @ -18,6 +18,14 @@ mix.js("resources/js/app.js", "public/js") | ||||
|         "resources/js/clients/invoices/action-selectors.js", | ||||
|         "public/js/clients/invoices/action-selectors.js" | ||||
|     ) | ||||
|     .js( | ||||
|         "resources/js/clients/purchase_orders/action-selectors.js", | ||||
|         "public/js/clients/purchase_orders/action-selectors.js" | ||||
|     ) | ||||
|     .js( | ||||
|         "resources/js/clients/purchase_orders/accept.js", | ||||
|         "public/js/clients/purchase_orders/accept.js" | ||||
|     ) | ||||
|     .js( | ||||
|         "resources/js/clients/invoices/payment.js", | ||||
|         "public/js/clients/invoices/payment.js" | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user