mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-11-01 20:17:35 -04:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/v5-develop' into v5-659
This commit is contained in:
		
						commit
						615002e6b0
					
				| @ -1 +1 @@ | ||||
| 5.3.7 | ||||
| 5.3.10 | ||||
| @ -24,12 +24,14 @@ use App\Models\Invoice; | ||||
| use App\Models\InvoiceInvitation; | ||||
| use App\Models\Payment; | ||||
| use App\Models\Paymentable; | ||||
| use App\Models\QuoteInvitation; | ||||
| use App\Models\RecurringInvoiceInvitation; | ||||
| use App\Utils\Ninja; | ||||
| use DB; | ||||
| use Exception; | ||||
| use Illuminate\Console\Command; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Mail; | ||||
| use Illuminate\Support\Str; | ||||
| use Mail; | ||||
| use Symfony\Component\Console\Input\InputOption; | ||||
| 
 | ||||
| /* | ||||
| @ -103,6 +105,7 @@ class CheckData extends Command | ||||
|         // $this->checkPaidToCompanyDates();
 | ||||
|         $this->checkClientBalances(); | ||||
|         $this->checkContacts(); | ||||
|         $this->checkEntityInvitations(); | ||||
|         $this->checkCompanyData(); | ||||
| 
 | ||||
| 
 | ||||
| @ -197,7 +200,7 @@ class CheckData extends Command | ||||
|                     ->where('id', '=', $contact->id) | ||||
|                     ->whereNull('contact_key') | ||||
|                     ->update([ | ||||
|                         'contact_key' => str_random(config('ninja.key_length')), | ||||
|                         'contact_key' => Str::random(config('ninja.key_length')), | ||||
|                     ]); | ||||
|             } | ||||
|         } | ||||
| @ -307,13 +310,73 @@ class CheckData extends Command | ||||
|                 $invitation->company_id = $invoice->company_id; | ||||
|                 $invitation->user_id = $invoice->user_id; | ||||
|                 $invitation->invoice_id = $invoice->id; | ||||
|                 $invitation->contact_id = ClientContact::whereClientId($invoice->client_id)->whereIsPrimary(true)->first()->id; | ||||
|                 $invitation->invitation_key = str_random(config('ninja.key_length')); | ||||
|                 $invitation->contact_id = ClientContact::whereClientId($invoice->client_id)->first()->id; | ||||
|                 $invitation->invitation_key = Str::random(config('ninja.key_length')); | ||||
|                 $invitation->save(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private function checkEntityInvitations() | ||||
|     { | ||||
|      | ||||
|     RecurringInvoiceInvitation::where('deleted_at',"0000-00-00 00:00:00.000000")->withTrashed()->update(['deleted_at' => null]); | ||||
|     InvoiceInvitation::where('deleted_at',"0000-00-00 00:00:00.000000")->withTrashed()->update(['deleted_at' => null]); | ||||
|     QuoteInvitation::where('deleted_at',"0000-00-00 00:00:00.000000")->withTrashed()->update(['deleted_at' => null]); | ||||
| 
 | ||||
|         $entities = ['invoice', 'quote', 'credit', 'recurring_invoice']; | ||||
| 
 | ||||
|         foreach($entities as $entity) | ||||
|         { | ||||
|             $table = "{$entity}s"; | ||||
|             $invitation_table = "{$entity}_invitations"; | ||||
| 
 | ||||
|         $entities = DB::table($table) | ||||
|                     ->leftJoin($invitation_table, function ($join) use($invitation_table, $table, $entity){ | ||||
|                         $join->on("{$invitation_table}.{$entity}_id", '=', "{$table}.id"); | ||||
|                              // ->whereNull("{$invitation_table}.deleted_at");
 | ||||
|                     }) | ||||
|                     ->groupBy("{$table}.id", "{$table}.user_id", "{$table}.company_id", "{$table}.client_id") | ||||
|                     ->havingRaw("count({$invitation_table}.id) = 0") | ||||
|                     ->get(["{$table}.id", "{$table}.user_id", "{$table}.company_id", "{$table}.client_id"]); | ||||
| 
 | ||||
| 
 | ||||
|         $this->logMessage($entities->count()." {$table} without any invitations"); | ||||
| 
 | ||||
|         if ($this->option('fix') == 'true')  | ||||
|             $this->fixInvitations($entities, $entity); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private function fixInvitations($entities, $entity) | ||||
|     { | ||||
|         $entity_key = "{$entity}_id"; | ||||
| 
 | ||||
|         $entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation'; | ||||
| 
 | ||||
|         foreach($entities as $entity) | ||||
|         { | ||||
|             $invitation = new $entity_obj(); | ||||
|             $invitation->company_id = $entity->company_id; | ||||
|             $invitation->user_id = $entity->user_id; | ||||
|             $invitation->{$entity_key} = $entity->id; | ||||
|             $invitation->client_contact_id = ClientContact::whereClientId($entity->client_id)->first()->id; | ||||
|             $invitation->key = Str::random(config('ninja.key_length')); | ||||
| 
 | ||||
|             try{ | ||||
|                 $invitation->save(); | ||||
|             } | ||||
|             catch(\Exception $e){ | ||||
|                 $invitation = null; | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     // private function checkPaidToCompanyDates()
 | ||||
|     // {
 | ||||
|     //     Company::cursor()->each(function ($company){
 | ||||
|  | ||||
| @ -15,12 +15,14 @@ class S3Cleanup extends Command | ||||
|      */ | ||||
|     protected $signature = 'ninja:s3-cleanup'; | ||||
| 
 | ||||
|     protected $log = ''; | ||||
| 
 | ||||
|     /** | ||||
|      * The console command description. | ||||
|      * | ||||
|      * @var string | ||||
|      */ | ||||
|     protected $description = 'Remove orphan folders'; | ||||
|     protected $description = 'Remove orphan folders/files'; | ||||
| 
 | ||||
|     /** | ||||
|      * Create a new command instance. | ||||
| @ -54,7 +56,11 @@ class S3Cleanup extends Command | ||||
|                 if(!in_array($dir, $merged)) | ||||
|                 { | ||||
|                     $this->logMessage("Deleting $dir"); | ||||
|                     Storage::disk(config('filesystems.default'))->deleteDirectory($dir); | ||||
| 
 | ||||
|                     /* Ensure we are not deleting the root folder */ | ||||
|                     if(strlen($dir) > 1) | ||||
|                         Storage::disk(config('filesystems.default'))->deleteDirectory($dir); | ||||
|                      | ||||
|                 } | ||||
|             }         | ||||
| 
 | ||||
|  | ||||
| @ -71,7 +71,8 @@ class Kernel extends ConsoleKernel | ||||
| 
 | ||||
|             $schedule->job(new AdjustEmailQuota)->dailyAt('23:00')->withoutOverlapping(); | ||||
|             $schedule->job(new SendFailedEmails)->daily()->withoutOverlapping(); | ||||
|             $schedule->command('ninja:check-data --database=db-ninja-02')->daily()->withoutOverlapping(); | ||||
|             $schedule->command('ninja:check-data --database=db-ninja-02')->dailyAt('00:15')->withoutOverlapping(); | ||||
|             $schedule->command('ninja:s3-cleanup')->dailyAt('23:15')->withoutOverlapping(); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										24
									
								
								app/Exceptions/SystemError.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/Exceptions/SystemError.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Exceptions; | ||||
| 
 | ||||
| use Exception; | ||||
| 
 | ||||
| class SystemError extends Exception | ||||
| { | ||||
|     public function report() | ||||
|     { | ||||
|         // ..
 | ||||
|     } | ||||
| 
 | ||||
|     public function render($request) | ||||
|     { | ||||
| 
 | ||||
|         return view('errors.guest', [ | ||||
|             'message' => $this->getMessage(), | ||||
|             'code' => $this->getCode(), | ||||
|         ]); | ||||
|          | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -106,12 +106,13 @@ class BaseController extends Controller | ||||
|           'user.company_user', | ||||
|           'token', | ||||
|           'company.activities', | ||||
|           'company.tax_rates', | ||||
|           'company.documents', | ||||
|           'company.company_gateways.gateway', | ||||
|           'company.users.company_user', | ||||
|           'company.tax_rates', | ||||
|           'company.groups', | ||||
|           'company.task_statuses', | ||||
|           'company.payment_terms', | ||||
|           'company.groups', | ||||
|           'company.designs.company', | ||||
|           'company.expense_categories', | ||||
|           'company.subscriptions', | ||||
| @ -314,8 +315,8 @@ class BaseController extends Controller | ||||
|                   $query->where('tasks.user_id', $user->id)->orWhere('tasks.assigned_user_id', $user->id); | ||||
| 
 | ||||
|             }, | ||||
|             'company.tax_rates' => function ($query) use ($updated_at, $user) { | ||||
|                 $query->where('updated_at', '>=', $updated_at); | ||||
|             'company.tax_rates'=> function ($query) use ($updated_at, $user) { | ||||
|                 $query->whereNotNull('updated_at'); | ||||
|             }, | ||||
|             'company.vendors'=> function ($query) use ($updated_at, $user) { | ||||
|                 $query->where('updated_at', '>=', $updated_at)->with('contacts', 'documents'); | ||||
| @ -328,7 +329,7 @@ class BaseController extends Controller | ||||
|                 $query->where('updated_at', '>=', $updated_at); | ||||
|             }, | ||||
|             'company.task_statuses'=> function ($query) use ($updated_at, $user) { | ||||
|                 $query->where('updated_at', '>=', $updated_at); | ||||
|                 $query->whereNotNull('updated_at'); | ||||
|             }, | ||||
|             'company.activities'=> function ($query) use($user) { | ||||
| 
 | ||||
| @ -390,7 +391,7 @@ class BaseController extends Controller | ||||
|             'company.documents'=> function ($query) use ($created_at, $user) { | ||||
|                 $query->where('created_at', '>=', $created_at); | ||||
|             }, | ||||
|             'company.groups' => function ($query) use ($created_at, $user) { | ||||
|             'company.groups'=> function ($query) use ($created_at, $user) { | ||||
|                 $query->where('created_at', '>=', $created_at)->with('documents'); | ||||
| 
 | ||||
|             }, | ||||
| @ -398,8 +399,8 @@ class BaseController extends Controller | ||||
|                 $query->where('created_at', '>=', $created_at); | ||||
| 
 | ||||
|             }, | ||||
|             'company.tax_rates' => function ($query) use ($created_at, $user) { | ||||
|                 $query->where('created_at', '>=', $created_at); | ||||
|             'company.tax_rates'=> function ($query) use ($created_at, $user) { | ||||
|                 $query->whereNotNull('created_at'); | ||||
| 
 | ||||
|             }, | ||||
|             'company.activities'=> function ($query) use($user) { | ||||
| @ -768,6 +769,10 @@ class BaseController extends Controller | ||||
|             case 'profile': | ||||
|                 return 'main.profile.dart.js';                                       | ||||
|             default: | ||||
| 
 | ||||
|                 if(Ninja::isSelfHost()) | ||||
|                     return 'main.foss.dart.js'; | ||||
| 
 | ||||
|                 return 'main.dart.js'; | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -79,7 +79,7 @@ class DocumentController extends Controller | ||||
|         $zip = new ZipStream(now() . '-documents.zip', $options); | ||||
| 
 | ||||
|         foreach ($documents as $document) { | ||||
|             $zip->addFileFromPath(basename($document->diskPath()), TempFile::path($document->diskPath())); | ||||
|             $zip->addFileFromPath(basename($document->diskPath()), TempFile::path($document->filePath())); | ||||
|         } | ||||
| 
 | ||||
|         $zip->finish(); | ||||
|  | ||||
| @ -18,10 +18,10 @@ use App\Libraries\MultiDB; | ||||
| use App\Models\ClientContact; | ||||
| use App\Models\Company; | ||||
| use App\Utils\Ninja; | ||||
| use Auth; | ||||
| use Illuminate\Contracts\Routing\ResponseFactory; | ||||
| use Illuminate\Http\Request; | ||||
| use Illuminate\Http\Response; | ||||
| use Illuminate\Support\Facades\Auth; | ||||
| 
 | ||||
| class NinjaPlanController extends Controller | ||||
| { | ||||
| @ -35,7 +35,7 @@ class NinjaPlanController extends Controller | ||||
| 
 | ||||
|         $account = $company->account; | ||||
| 
 | ||||
|         if (Ninja::isHosted() && MultiDB::findAndSetDbByContactKey($contact_key) && $client_contact = ClientContact::where('contact_key', $contact_key)->first()) | ||||
|         if (MultiDB::findAndSetDbByContactKey($contact_key) && $client_contact = ClientContact::where('contact_key', $contact_key)->first()) | ||||
|         {             | ||||
|          | ||||
|             nlog("Ninja Plan Controller - Found and set Client Contact"); | ||||
|  | ||||
| @ -15,6 +15,7 @@ use App\DataMapper\Analytics\AccountDeleted; | ||||
| use App\DataMapper\CompanySettings; | ||||
| use App\DataMapper\DefaultSettings; | ||||
| use App\Http\Requests\Company\CreateCompanyRequest; | ||||
| use App\Http\Requests\Company\DefaultCompanyRequest; | ||||
| use App\Http\Requests\Company\DestroyCompanyRequest; | ||||
| use App\Http\Requests\Company\EditCompanyRequest; | ||||
| use App\Http\Requests\Company\ShowCompanyRequest; | ||||
| @ -598,8 +599,66 @@ class CompanyController extends BaseController | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     // public function default(DefaultCompanyRequest $request, Company $company)
 | ||||
|     // {
 | ||||
|          | ||||
|     // }
 | ||||
| /** | ||||
|      * Update the specified resource in storage. | ||||
|      * | ||||
|      * @param UploadCompanyRequest $request | ||||
|      * @param Company $client | ||||
|      * @return Response | ||||
|      * | ||||
|      * | ||||
|      * | ||||
|      * @OA\Post( | ||||
|      *      path="/api/v1/companies/{company}/default", | ||||
|      *      operationId="setDefaultCompany", | ||||
|      *      tags={"companies"}, | ||||
|      *      summary="Sets the company as the default company.", | ||||
|      *      description="Sets the company as the default company.", | ||||
|      *      @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), | ||||
|      *      @OA\Parameter(ref="#/components/parameters/X-Api-Token"), | ||||
|      *      @OA\Parameter(ref="#/components/parameters/X-Requested-With"), | ||||
|      *      @OA\Parameter(ref="#/components/parameters/include"), | ||||
|      *      @OA\Parameter( | ||||
|      *          name="company", | ||||
|      *          in="path", | ||||
|      *          description="The Company Hashed ID", | ||||
|      *          example="D2J234DFA", | ||||
|      *          required=true, | ||||
|      *          @OA\Schema( | ||||
|      *              type="string", | ||||
|      *              format="string", | ||||
|      *          ), | ||||
|      *      ), | ||||
|      *      @OA\Response( | ||||
|      *          response=200, | ||||
|      *          description="Returns the company object", | ||||
|      *          @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), | ||||
|      *          @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), | ||||
|      *          @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), | ||||
|      *          @OA\JsonContent(ref="#/components/schemas/Company"), | ||||
|      *       ), | ||||
|      *       @OA\Response( | ||||
|      *          response=422, | ||||
|      *          description="Validation error", | ||||
|      *          @OA\JsonContent(ref="#/components/schemas/ValidationError"), | ||||
|      * | ||||
|      *       ), | ||||
|      *       @OA\Response( | ||||
|      *           response="default", | ||||
|      *           description="Unexpected Error", | ||||
|      *           @OA\JsonContent(ref="#/components/schemas/Error"), | ||||
|      *       ), | ||||
|      *     ) | ||||
|      */ | ||||
|     public function default(DefaultCompanyRequest $request, Company $company) | ||||
|     { | ||||
| 
 | ||||
|         $account = $company->account; | ||||
|         $account->default_company_id = $company->id; | ||||
|         $account->save();   | ||||
| 
 | ||||
|         return $this->itemResponse($company->fresh()); | ||||
|        | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -12,6 +12,7 @@ | ||||
| namespace App\Http\Controllers; | ||||
| 
 | ||||
| use App\DataMapper\FeesAndLimits; | ||||
| use App\Exceptions\SystemError; | ||||
| use App\Factory\CompanyGatewayFactory; | ||||
| use App\Http\Requests\StripeConnect\InitializeStripeConnectRequest; | ||||
| use App\Libraries\MultiDB; | ||||
| @ -20,6 +21,7 @@ use App\Models\Company; | ||||
| use App\Models\CompanyGateway; | ||||
| use App\Models\GatewayType; | ||||
| use App\PaymentDrivers\Stripe\Connect\Account; | ||||
| use Exception; | ||||
| use Illuminate\Http\Request; | ||||
| use Stripe\Exception\ApiErrorException; | ||||
| 
 | ||||
| @ -78,7 +80,7 @@ class StripeConnectController extends BaseController | ||||
|         { | ||||
|              | ||||
|             nlog($e->getMessage()); | ||||
|          | ||||
|             throw new SystemError($e->getMessage(), 500); | ||||
|         } | ||||
|          | ||||
|         MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']); | ||||
|  | ||||
| @ -44,6 +44,7 @@ class CreditsTable extends Component | ||||
|                       ->orWhereNull('due_date'); | ||||
|             }) | ||||
|             ->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc') | ||||
|             ->withTrashed() | ||||
|             ->paginate($this->per_page); | ||||
| 
 | ||||
|         return render('components.livewire.credits-table', [ | ||||
|  | ||||
| @ -53,7 +53,10 @@ class DocumentsTable extends Component | ||||
|     public function render() | ||||
|     { | ||||
|         return render('components.livewire.documents-table', [ | ||||
|             'documents' => $this->query->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')->paginate($this->per_page), | ||||
|             'documents' => $this->query | ||||
|                 ->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc') | ||||
|                 ->withTrashed() | ||||
|                 ->paginate($this->per_page), | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -76,6 +76,7 @@ class InvoicesTable extends Component | ||||
|         $query = $query | ||||
|             ->where('client_id', auth('contact')->user()->client->id) | ||||
|             ->where('status_id', '<>', Invoice::STATUS_DRAFT) | ||||
|             ->where('status_id', '<>', Invoice::STATUS_CANCELLED) | ||||
|             ->withTrashed() | ||||
|             ->paginate($this->per_page); | ||||
| 
 | ||||
|  | ||||
| @ -37,6 +37,7 @@ class PaymentMethodsTable extends Component | ||||
|             ->where('company_id', $this->company->id) | ||||
|             ->where('client_id', $this->client->id) | ||||
|             ->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc') | ||||
|             ->withTrashed() | ||||
|             ->paginate($this->per_page); | ||||
| 
 | ||||
|         return render('components.livewire.payment-methods-table', [ | ||||
|  | ||||
| @ -48,6 +48,7 @@ class QuotesTable extends Component | ||||
|             ->where('company_id', $this->company->id) | ||||
|             ->where('client_id', auth('contact')->user()->client->id) | ||||
|             ->where('status_id', '<>', Quote::STATUS_DRAFT) | ||||
|             ->withTrashed() | ||||
|             ->paginate($this->per_page); | ||||
| 
 | ||||
|         return render('components.livewire.quotes-table', [ | ||||
|  | ||||
| @ -46,6 +46,7 @@ class RecurringInvoicesTable extends Component | ||||
|             ->orderBy('status_id', 'asc') | ||||
|             ->with('client') | ||||
|             ->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc') | ||||
|             ->withTrashed() | ||||
|             ->paginate($this->per_page); | ||||
| 
 | ||||
|         return render('components.livewire.recurring-invoices-table', [ | ||||
|  | ||||
| @ -39,6 +39,7 @@ class SubscriptionRecurringInvoicesTable extends Component | ||||
|             ->where('company_id', $this->company->id) | ||||
|             ->whereNotNull('subscription_id') | ||||
|             ->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc') | ||||
|             ->withTrashed() | ||||
|             ->paginate($this->per_page); | ||||
| 
 | ||||
|         return render('components.livewire.subscriptions-recurring-invoices-table', [ | ||||
|  | ||||
| @ -48,6 +48,7 @@ class TasksTable extends Component | ||||
| 
 | ||||
|         $query = $query | ||||
|             ->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc') | ||||
|             ->withTrashed() | ||||
|             ->paginate($this->per_page); | ||||
| 
 | ||||
|         return render('components.livewire.tasks-table', [ | ||||
|  | ||||
| @ -41,6 +41,10 @@ class ContactKeyLogin | ||||
| 
 | ||||
|         if ($request->segment(2) && $request->segment(2) == 'magic_link' && $request->segment(3)) { | ||||
|             $payload = Cache::get($request->segment(3)); | ||||
| 
 | ||||
|             if(!$payload) | ||||
|                 abort(403, 'Link expired.'); | ||||
| 
 | ||||
|             $contact_email = $payload['email']; | ||||
|              | ||||
|             if($client_contact = ClientContact::where('email', $contact_email)->where('company_id', $payload['company_id'])->first()){ | ||||
|  | ||||
| @ -55,7 +55,7 @@ class QueryLogging | ||||
|             // nlog("Query count = {$count}");
 | ||||
|          | ||||
|              if($count > 175){ | ||||
|                  nlog("Quer count = {$count}"); | ||||
|                  nlog("Query count = {$count}"); | ||||
|                  nlog($queries); | ||||
|              } | ||||
|                          | ||||
|  | ||||
| @ -74,7 +74,6 @@ class StoreClientRequest extends Request | ||||
|         $rules['number'] = ['nullable',Rule::unique('clients')->where('company_id', auth()->user()->company()->id)]; | ||||
|         $rules['id_number'] = ['nullable',Rule::unique('clients')->where('company_id', auth()->user()->company()->id)]; | ||||
| 
 | ||||
| 
 | ||||
|         return $rules; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										36
									
								
								app/Http/Requests/Company/DefaultCompanyRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								app/Http/Requests/Company/DefaultCompanyRequest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| <?php | ||||
| /** | ||||
|  * 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 | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Http\Requests\Company; | ||||
| 
 | ||||
| use App\Http\Requests\Request; | ||||
| 
 | ||||
| class DefaultCompanyRequest extends Request | ||||
| { | ||||
|     /** | ||||
|      * Determine if the user is authorized to make this request. | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function authorize() : bool | ||||
|     { | ||||
|         return auth()->user()->isAdmin() | ||||
|     } | ||||
| 
 | ||||
|     public function rules() | ||||
|     { | ||||
|     	 | ||||
|     	$rules = []; | ||||
| 
 | ||||
|     	return $rules; | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| namespace App\Http\Requests\Gateways\Checkout3ds; | ||||
| 
 | ||||
| use App\Libraries\MultiDB; | ||||
| use App\Models\Client; | ||||
| use App\Models\Company; | ||||
| use App\Models\CompanyGateway; | ||||
| @ -37,6 +38,7 @@ class Checkout3dsRequest extends FormRequest | ||||
| 
 | ||||
|     public function getCompany() | ||||
|     { | ||||
|         MultiDB::findAndSetDbByCompanyKey($this->company_key); | ||||
|         return Company::where('company_key', $this->company_key)->first(); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -136,6 +136,10 @@ class Request extends FormRequest | ||||
| 
 | ||||
|         if (isset($input['contacts']) && is_array($input['contacts'])) { | ||||
|             foreach ($input['contacts'] as $key => $contact) { | ||||
| 
 | ||||
|                 if(!is_array($contact)) | ||||
|                     continue; | ||||
| 
 | ||||
|                 if (array_key_exists('id', $contact) && is_numeric($contact['id'])) { | ||||
|                     unset($input['contacts'][$key]['id']); | ||||
|                 } elseif (array_key_exists('id', $contact) && is_string($contact['id'])) { | ||||
| @ -154,6 +158,7 @@ class Request extends FormRequest | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|             } | ||||
|         } | ||||
|          | ||||
|  | ||||
| @ -67,7 +67,7 @@ class InvoiceTransformer extends BaseTransformer { | ||||
| 
 | ||||
| 		if ( $transformed['balance'] < $transformed['amount'] ) { | ||||
| 			$transformed['payments'] = [[ | ||||
| 				'date'   => date( 'Y-m-d' ), | ||||
| 				'date'   => isset( $invoice_data['Last Payment Date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['Invoice Date'] ) ) : date( 'Y-m-d' ), | ||||
| 				'amount' => $transformed['amount'] - $transformed['balance'], | ||||
| 			]]; | ||||
| 		} | ||||
| @ -75,3 +75,4 @@ class InvoiceTransformer extends BaseTransformer { | ||||
| 		return $transformed; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -36,6 +36,7 @@ use Illuminate\Support\Facades\Cache; | ||||
| use Illuminate\Support\Str; | ||||
| use Symfony\Component\HttpFoundation\Response; | ||||
| use Turbo124\Beacon\Facades\LightLogs; | ||||
| use Illuminate\Support\Facades\App; | ||||
| 
 | ||||
| class CreateAccount | ||||
| { | ||||
| @ -114,8 +115,22 @@ class CreateAccount | ||||
| 
 | ||||
|         $spaa9f78->fresh(); | ||||
| 
 | ||||
|         if(Ninja::isHosted()) | ||||
|         if(Ninja::isHosted()){ | ||||
| nlog("welcome"); | ||||
|             App::forgetInstance('translator'); | ||||
|             $t = app('translator'); | ||||
|             $t->replace(Ninja::transformTranslations($sp035a66->settings)); | ||||
| 
 | ||||
|             $nmo = new NinjaMailerObject; | ||||
|             $nmo->mailable = new \Modules\Admin\Mail\Welcome($sp035a66->owner()); | ||||
|             $nmo->company =  $sp035a66; | ||||
|             $nmo->settings = $sp035a66->settings; | ||||
|             $nmo->to_user = $sp035a66->owner(); | ||||
| 
 | ||||
|             NinjaMailerJob::dispatch($nmo); | ||||
| 
 | ||||
|             \Modules\Admin\Jobs\Account\NinjaUser::dispatch([], $sp035a66); | ||||
|         } | ||||
| 
 | ||||
|         VersionCheck::dispatch(); | ||||
| 
 | ||||
| @ -123,6 +138,9 @@ class CreateAccount | ||||
|                  ->increment() | ||||
|                  ->batch(); | ||||
| 
 | ||||
|          | ||||
| 
 | ||||
| 
 | ||||
|         return $sp794f3f; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -483,7 +483,7 @@ class CompanyImport implements ShouldQueue | ||||
|     { | ||||
| 
 | ||||
|         $this->genericImport(Client::class,  | ||||
|             ['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id', 'gateway_tokens', 'contacts', 'documents'],  | ||||
|             ['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id', 'gateway_tokens', 'contacts', 'documents','country'],  | ||||
|             [['users' => 'user_id'], ['users' => 'assigned_user_id']],  | ||||
|             'clients', | ||||
|             'number'); | ||||
| @ -496,7 +496,7 @@ class CompanyImport implements ShouldQueue | ||||
|     { | ||||
| 
 | ||||
|         $this->genericImport(ClientContact::class,  | ||||
|             ['user_id', 'company_id', 'id', 'hashed_id'],  | ||||
|             ['user_id', 'company_id', 'id', 'hashed_id','company'],  | ||||
|             [['users' => 'user_id'], ['clients' => 'client_id']],  | ||||
|             'client_contacts', | ||||
|             'email'); | ||||
|  | ||||
| @ -121,18 +121,29 @@ class NinjaMailerJob implements ShouldQueue | ||||
| 
 | ||||
|             $message = $e->getMessage(); | ||||
| 
 | ||||
|             /** | ||||
|              * Post mark buries the proper message in a a guzzle response | ||||
|              * this merges a text string with a json object | ||||
|              * need to harvest the ->Message property using the following | ||||
|              */ | ||||
|             if($e instanceof ClientException) { //postmark specific failure
 | ||||
| 
 | ||||
|                 $response = $e->getResponse(); | ||||
|                 $message_body = json_decode($response->getBody()->getContents()); | ||||
|                  | ||||
|                 if(property_exists($message_body, 'Message')){ | ||||
|                     $message = $message_body->Message; | ||||
|                     nlog($message); | ||||
|                 } | ||||
|                  | ||||
|                 nlog($response); | ||||
|                 // $message = $response->Message;
 | ||||
|             } | ||||
| 
 | ||||
|             /* If the is an entity attached to the message send a failure mailer */ | ||||
|             if($this->nmo->entity) | ||||
|                 $this->entityEmailFailed($message); | ||||
| 
 | ||||
|             if(Ninja::isHosted() && (!$e instanceof ClientException)) // Don't send postmark failures to Sentry
 | ||||
|             /* Don't send postmark failures to Sentry */ | ||||
|             if(Ninja::isHosted() && (!$e instanceof ClientException))  | ||||
|                 app('sentry')->captureException($e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -82,8 +82,7 @@ class CreateUser | ||||
|             'settings' => null, | ||||
|         ]); | ||||
| 
 | ||||
|         if(!Ninja::isSelfHost()){ | ||||
|             nlog("in the create user class"); | ||||
|         if(!Ninja::isSelfHost()) { | ||||
|             event(new UserWasCreated($user, $user, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -233,7 +233,7 @@ class Import implements ShouldQueue | ||||
|         $account->save(); | ||||
| 
 | ||||
|         //company size check
 | ||||
|         if ($this->company->invoices()->count() > 1000 || $this->company->products()->count() > 1000 || $this->company->clients()->count() > 1000) { | ||||
|         if ($this->company->invoices()->count() > 500 || $this->company->products()->count() > 500 || $this->company->clients()->count() > 500) { | ||||
|             $this->company->is_large = true; | ||||
|             $this->company->save(); | ||||
|         } | ||||
| @ -261,9 +261,6 @@ class Import implements ShouldQueue | ||||
|          | ||||
|         /*After a migration first some basic jobs to ensure the system is up to date*/ | ||||
|         VersionCheck::dispatch(); | ||||
|          | ||||
|         // CreateCompanyPaymentTerms::dispatchNow($sp035a66, $spaa9f78);
 | ||||
|         // CreateCompanyTaskStatuses::dispatchNow($this->company, $this->user);
 | ||||
| 
 | ||||
|         info('Completed🚀🚀🚀🚀🚀 at '.now()); | ||||
| 
 | ||||
| @ -640,7 +637,8 @@ class Import implements ShouldQueue | ||||
|                 $client->updated_at = Carbon::parse($modified['updated_at']); | ||||
| 
 | ||||
|             $client->save(['timestamps' => false]); | ||||
| 
 | ||||
|             $client->fresh(); | ||||
|              | ||||
|             $client->contacts()->forceDelete(); | ||||
| 
 | ||||
|             if (array_key_exists('contacts', $resource)) { // need to remove after importing new migration.json
 | ||||
| @ -650,7 +648,7 @@ class Import implements ShouldQueue | ||||
|                     $modified_contacts[$key]['company_id'] = $this->company->id; | ||||
|                     $modified_contacts[$key]['user_id'] = $this->processUserId($resource); | ||||
|                     $modified_contacts[$key]['client_id'] = $client->id; | ||||
|                     $modified_contacts[$key]['password'] = 'mysuperpassword'; // @todo, and clean up the code..
 | ||||
|                     $modified_contacts[$key]['password'] = Str::random(8);  | ||||
|                     unset($modified_contacts[$key]['id']); | ||||
|                 } | ||||
| 
 | ||||
| @ -685,6 +683,8 @@ class Import implements ShouldQueue | ||||
|                 'old' => $resource['id'], | ||||
|                 'new' => $client->id, | ||||
|             ]; | ||||
| 
 | ||||
|             $client = null; | ||||
|         } | ||||
| 
 | ||||
|         Client::reguard(); | ||||
|  | ||||
| @ -88,7 +88,7 @@ class PaymentNotification implements ShouldQueue | ||||
|         $client = $payment->client; | ||||
|         $amount = $payment->amount; | ||||
| 
 | ||||
|         if ($invoice) { | ||||
|         if ($invoice && $invoice->line_items) { | ||||
|             $items = $invoice->line_items; | ||||
|             $item = end($items)->product_key; | ||||
|             $entity_number = $invoice->number; | ||||
|  | ||||
							
								
								
									
										47
									
								
								app/Listeners/Quote/QuoteApprovedWebhook.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								app/Listeners/Quote/QuoteApprovedWebhook.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Quote Ninja (https://quoteninja.com). | ||||
|  * | ||||
|  * @link https://github.com/quoteninja/quoteninja source repository | ||||
|  * | ||||
|  * @copyright Copyright (c) 2021. Quote Ninja LLC (https://quoteninja.com) | ||||
|  * | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Listeners\Quote; | ||||
| 
 | ||||
| use App\Jobs\Util\WebhookHandler; | ||||
| use App\Libraries\MultiDB; | ||||
| use App\Models\Webhook; | ||||
| use Illuminate\Contracts\Queue\ShouldQueue; | ||||
| 
 | ||||
| class QuoteApprovedWebhook implements ShouldQueue | ||||
| { | ||||
| 
 | ||||
|     public function __construct() | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Handle the event. | ||||
|      * | ||||
|      * @param  object  $event | ||||
|      * @return void | ||||
|      */ | ||||
|     public function handle($event) | ||||
|     { | ||||
|         MultiDB::setDb($event->company->db); | ||||
| 
 | ||||
|         $quote = $event->quote; | ||||
| 
 | ||||
|         $subscriptions = Webhook::where('company_id', $quote->company_id) | ||||
|                         ->where('event_id', Webhook::EVENT_APPROVE_QUOTE) | ||||
|                         ->exists(); | ||||
| 
 | ||||
|         if ($subscriptions) { | ||||
|             WebhookHandler::dispatch(Webhook::EVENT_APPROVE_QUOTE, $quote, $quote->company); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -24,6 +24,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; | ||||
| use Illuminate\Foundation\Events\Dispatchable; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| use Illuminate\Support\Facades\App; | ||||
| use Illuminate\Support\Carbon; | ||||
| 
 | ||||
| class SendVerificationNotification implements ShouldQueue | ||||
| { | ||||
| @ -53,17 +54,20 @@ class SendVerificationNotification implements ShouldQueue | ||||
| 
 | ||||
|         $event->user->service()->invite($event->company); | ||||
| 
 | ||||
|         App::forgetInstance('translator'); | ||||
|         $t = app('translator'); | ||||
|         $t->replace(Ninja::transformTranslations($event->company->settings)); | ||||
| 
 | ||||
|         $nmo = new NinjaMailerObject; | ||||
|         $nmo->mailable = new UserAdded($event->company, $event->creating_user, $event->user); | ||||
|         $nmo->company = $event->company; | ||||
|         $nmo->settings = $event->company->settings; | ||||
|         $nmo->to_user = $event->creating_user; | ||||
|         NinjaMailerJob::dispatch($nmo); | ||||
|         if(Carbon::parse($event->company->created_at)->lt(now()->subDay())) | ||||
|         { | ||||
|             App::forgetInstance('translator'); | ||||
|             $t = app('translator'); | ||||
|             $t->replace(Ninja::transformTranslations($event->company->settings)); | ||||
| 
 | ||||
|             $nmo = new NinjaMailerObject; | ||||
|             $nmo->mailable = new UserAdded($event->company, $event->creating_user, $event->user); | ||||
|             $nmo->company = $event->company; | ||||
|             $nmo->settings = $event->company->settings; | ||||
|             $nmo->to_user = $event->creating_user; | ||||
|             NinjaMailerJob::dispatch($nmo); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -62,9 +62,10 @@ class SupportMessageSent extends Mailable | ||||
|         $company = auth()->user()->company(); | ||||
|         $user = auth()->user(); | ||||
|         $db = str_replace("db-ninja-", "", $company->db); | ||||
| 
 | ||||
|         $is_large = $company->is_large ? "L" : ""; | ||||
|          | ||||
|         if(Ninja::isHosted()) | ||||
|             $subject = "{$priority}Hosted-{$db}-[{$company->is_large}] :: {$plan} :: ".date('M jS, g:ia'); | ||||
|             $subject = "{$priority}Hosted-{$db}{$is_large} :: {$plan} :: ".date('M jS, g:ia'); | ||||
|         else | ||||
|             $subject = "{$priority}Self Hosted :: {$plan} :: ".date('M jS, g:ia'); | ||||
| 
 | ||||
|  | ||||
| @ -123,10 +123,14 @@ class TemplateEmail extends Mailable | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|         if($this->invitation->invoice && $settings->ubl_email_attachment && $this->company->account->hasFeature(Account::FEATURE_DOCUMENTS)){ | ||||
|         if($this->invitation && $this->invitation->invoice && $settings->ubl_email_attachment && $this->company->account->hasFeature(Account::FEATURE_DOCUMENTS)){ | ||||
| 
 | ||||
|             $ubl_string = CreateUbl::dispatchNow($this->invitation->invoice); | ||||
|             $this->attachData($ubl_string, $this->invitation->invoice->getFileName('xml')); | ||||
| 
 | ||||
|             nlog($ubl_string); | ||||
|              | ||||
|             if($ubl_string) | ||||
|                 $this->attachData($ubl_string, $this->invitation->invoice->getFileName('xml')); | ||||
|              | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -90,7 +90,7 @@ class Client extends BaseModel implements HasLocalePreference | ||||
|         'contacts.company', | ||||
|         // 'currency',
 | ||||
|         // 'primary_contact',
 | ||||
|         // 'country',
 | ||||
|         'country', | ||||
|         // 'contacts',
 | ||||
|         // 'shipping_country',
 | ||||
|         // 'company',
 | ||||
| @ -218,7 +218,7 @@ class Client extends BaseModel implements HasLocalePreference | ||||
| 
 | ||||
|     public function assigned_user() | ||||
|     { | ||||
|         return $this->belongsTo(User::class, 'assigned_user_id', 'id'); | ||||
|         return $this->belongsTo(User::class, 'assigned_user_id', 'id')->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     public function country() | ||||
| @ -361,6 +361,9 @@ class Client extends BaseModel implements HasLocalePreference | ||||
|             if (is_string($this->settings->{$setting}) && (iconv_strlen($this->settings->{$setting}) >= 1)) { | ||||
|                 return $this->settings->{$setting}; | ||||
|             } | ||||
|             elseif(is_bool($this->settings->{$setting})){ | ||||
|                 return $this->settings->{$setting}; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /*Group Settings*/ | ||||
|  | ||||
| @ -92,7 +92,7 @@ class ClientContact extends Authenticatable implements HasLocalePreference | ||||
|         'custom_value4', | ||||
|         'email', | ||||
|         'is_primary', | ||||
|         'client_id', | ||||
|         // 'client_id',
 | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -13,10 +13,12 @@ namespace App\Models; | ||||
| 
 | ||||
| use App\Utils\Traits\MakesDates; | ||||
| use Illuminate\Database\Eloquent\Model; | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| 
 | ||||
| class ClientGatewayToken extends BaseModel | ||||
| { | ||||
|     use MakesDates; | ||||
|     use SoftDeletes; | ||||
| 
 | ||||
|     protected $casts = [ | ||||
|         'meta' => 'object', | ||||
|  | ||||
| @ -281,7 +281,7 @@ class Company extends BaseModel | ||||
|      */ | ||||
|     public function company_gateways() | ||||
|     { | ||||
|         return $this->hasMany(CompanyGateway::class); | ||||
|         return $this->hasMany(CompanyGateway::class)->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -289,7 +289,7 @@ class Company extends BaseModel | ||||
|      */ | ||||
|     public function tax_rates() | ||||
|     { | ||||
|         return $this->hasMany(TaxRate::class); | ||||
|         return $this->hasMany(TaxRate::class)->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -297,7 +297,7 @@ class Company extends BaseModel | ||||
|      */ | ||||
|     public function products() | ||||
|     { | ||||
|         return $this->hasMany(Product::class); | ||||
|         return $this->hasMany(Product::class)->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -311,7 +311,7 @@ class Company extends BaseModel | ||||
| 
 | ||||
|     public function group_settings() | ||||
|     { | ||||
|         return $this->hasMany(GroupSetting::class); | ||||
|         return $this->hasMany(GroupSetting::class)->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     public function timezone() | ||||
|  | ||||
| @ -36,7 +36,7 @@ class CompanyLedger extends Model | ||||
| 
 | ||||
|     public function user() | ||||
|     { | ||||
|         return $this->belongsTo(User::class); | ||||
|         return $this->belongsTo(User::class)->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     public function company() | ||||
|  | ||||
| @ -23,6 +23,8 @@ class CompanyToken extends BaseModel | ||||
|     ]; | ||||
| 
 | ||||
|     protected $with = [ | ||||
|         'company', | ||||
|         'user' | ||||
|     ]; | ||||
| 
 | ||||
|     protected $touches = []; | ||||
|  | ||||
| @ -56,10 +56,10 @@ class CompanyUser extends Pivot | ||||
|         return self::class; | ||||
|     } | ||||
| 
 | ||||
|     public function tax_rates() | ||||
|     { | ||||
|         return $this->hasMany(TaxRate::class, 'company_id', 'company_id'); | ||||
|     } | ||||
|     // public function tax_rates()
 | ||||
|     // {
 | ||||
|     //     return $this->hasMany(TaxRate::class, 'company_id', 'company_id');
 | ||||
|     // }
 | ||||
| 
 | ||||
|     public function account() | ||||
|     { | ||||
| @ -78,7 +78,7 @@ class CompanyUser extends Pivot | ||||
| 
 | ||||
|     public function user() | ||||
|     { | ||||
|         return $this->belongsTo(User::class); | ||||
|         return $this->belongsTo(User::class)->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     public function company() | ||||
|  | ||||
| @ -120,7 +120,7 @@ class Credit extends BaseModel | ||||
| 
 | ||||
|     public function assigned_user() | ||||
|     { | ||||
|         return $this->belongsTo(User::class, 'assigned_user_id', 'id'); | ||||
|         return $this->belongsTo(User::class, 'assigned_user_id', 'id')->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     public function history() | ||||
|  | ||||
| @ -84,7 +84,7 @@ class Expense extends BaseModel | ||||
| 
 | ||||
|     public function assigned_user() | ||||
|     { | ||||
|         return $this->belongsTo(User::class, 'assigned_user_id', 'id'); | ||||
|         return $this->belongsTo(User::class, 'assigned_user_id', 'id')->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     public function company() | ||||
|  | ||||
| @ -52,7 +52,7 @@ class GroupSetting extends StaticModel | ||||
| 
 | ||||
|     public function user() | ||||
|     { | ||||
|         return $this->belongsTo(User::class); | ||||
|         return $this->belongsTo(User::class)->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     public function clients() | ||||
|  | ||||
| @ -45,6 +45,11 @@ class Proposal extends BaseModel | ||||
|         return $this->morphMany(Document::class, 'documentable'); | ||||
|     } | ||||
| 
 | ||||
|     public function user() | ||||
|     { | ||||
|         return $this->belongsTo(User::class)->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     public function assigned_user() | ||||
|     { | ||||
|         return $this->belongsTo(User::class, 'assigned_user_id', 'id'); | ||||
|  | ||||
| @ -76,7 +76,7 @@ class Subscription extends BaseModel | ||||
| 
 | ||||
|     public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo | ||||
|     { | ||||
|         return $this->belongsTo(User::class); | ||||
|         return $this->belongsTo(User::class)->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     public function nextDateByInterval($date, $frequency_id) | ||||
|  | ||||
| @ -66,7 +66,7 @@ class Task extends BaseModel | ||||
| 
 | ||||
|     public function user() | ||||
|     { | ||||
|         return $this->belongsTo(User::class); | ||||
|         return $this->belongsTo(User::class)->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     public function client() | ||||
|  | ||||
| @ -24,7 +24,7 @@ class TaxRate extends BaseModel | ||||
|         'rate', | ||||
|     ]; | ||||
| 
 | ||||
|     protected $appends = ['tax_rate_id']; | ||||
|     // protected $appends = ['tax_rate_id'];
 | ||||
| 
 | ||||
|     public function getEntityType() | ||||
|     { | ||||
|  | ||||
| @ -82,7 +82,7 @@ class Vendor extends BaseModel | ||||
| 
 | ||||
|     public function assigned_user() | ||||
|     { | ||||
|         return $this->belongsTo(User::class, 'assigned_user_id', 'id'); | ||||
|         return $this->belongsTo(User::class, 'assigned_user_id', 'id')->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     public function contacts() | ||||
|  | ||||
| @ -87,7 +87,7 @@ class Webhook extends BaseModel | ||||
| 
 | ||||
|     public function user() | ||||
|     { | ||||
|         return $this->belongsTo(User::class); | ||||
|         return $this->belongsTo(User::class)->withTrashed(); | ||||
|     } | ||||
| 
 | ||||
|     public function company() | ||||
|  | ||||
| @ -11,6 +11,8 @@ | ||||
| 
 | ||||
| namespace App\Notifications; | ||||
| 
 | ||||
| use App\Jobs\Invoice\CreateUbl; | ||||
| use App\Models\Invoice; | ||||
| use App\Utils\TempFile; | ||||
| use App\Utils\Traits\MakesInvoiceHtml; | ||||
| use Illuminate\Bus\Queueable; | ||||
|  | ||||
| @ -46,7 +46,7 @@ class InvoiceObserver | ||||
|      * @return void | ||||
|      */ | ||||
|     public function updated(Invoice $invoice) | ||||
|     {nlog("updated"); | ||||
|     { | ||||
|         $subscriptions = Webhook::where('company_id', $invoice->company->id) | ||||
|                             ->where('event_id', Webhook::EVENT_UPDATE_INVOICE) | ||||
|                             ->exists(); | ||||
|  | ||||
| @ -205,7 +205,6 @@ class BaseDriver extends AbstractPaymentDriver | ||||
| 
 | ||||
|         $invoices->each(function ($invoice) use ($payment) { | ||||
|             event(new InvoiceWasPaid($invoice, $payment, $payment->company, Ninja::eventVars())); | ||||
|             $invoice->service()->workFlow(); | ||||
|         }); | ||||
| 
 | ||||
|         return $payment->service()->applyNumber()->save(); | ||||
|  | ||||
| @ -136,30 +136,19 @@ class CreditCard | ||||
| 
 | ||||
|         $gateway_response = \json_decode($data['gateway_response']); | ||||
| 
 | ||||
|         try { | ||||
|             $payment_method = $this->braintree->gateway->paymentMethod()->create([ | ||||
|                 'customerId' => $customerId, | ||||
|                 'paymentMethodNonce' => $gateway_response->nonce, | ||||
|                 'options' => [ | ||||
|                     'verifyCard' => true, | ||||
|                 ], | ||||
|             ]); | ||||
|      | ||||
|             return $payment_method->paymentMethod->token; | ||||
|         } catch(\Exception $e) { | ||||
|             SystemLogger::dispatch( | ||||
|                 $e->getMessage(), | ||||
|                 SystemLog::CATEGORY_GATEWAY_RESPONSE, | ||||
|                 SystemLog::EVENT_GATEWAY_FAILURE, | ||||
|                 SystemLog::TYPE_BRAINTREE, | ||||
|                 $this->braintree->client, | ||||
|                 $this->braintree->client->company, | ||||
|             ); | ||||
|         $response = $this->braintree->gateway->paymentMethod()->create([ | ||||
|             'customerId' => $customerId, | ||||
|             'paymentMethodNonce' => $gateway_response->nonce, | ||||
|             'options' => [ | ||||
|                 'verifyCard' => true, | ||||
|             ], | ||||
|         ]); | ||||
| 
 | ||||
|             nlog(['e' => $e->getMessage(), 'class' => \get_class($e)]); | ||||
|      | ||||
|             throw new PaymentFailed($e->getMessage(), $e->getCode()); | ||||
|         if ($response->success) { | ||||
|             return $response->paymentMethod->token; | ||||
|         } | ||||
| 
 | ||||
|         throw new PaymentFailed($response->message); | ||||
|     } | ||||
| 
 | ||||
|     private function processSuccessfulPayment($response) | ||||
|  | ||||
| @ -49,7 +49,7 @@ class PayFastPaymentDriver extends BaseDriver | ||||
|     { | ||||
|         $types = []; | ||||
| 
 | ||||
|         if($this->client->currency()->code == 'ZAR') | ||||
|         if($this->client->currency()->code == 'ZAR' || $this->client->currency()->code == 'USD') | ||||
|             $types[] = GatewayType::CREDIT_CARD; | ||||
| 
 | ||||
|         return $types; | ||||
|  | ||||
| @ -76,7 +76,8 @@ class CreditCard | ||||
| 
 | ||||
|     private function decodeUnicodeString($string) | ||||
|     { | ||||
|         return iconv("UTF-8", "ISO-8859-1//TRANSLIT", $this->decode_encoded_utf8($string)); | ||||
|         return html_entity_decode($string, ENT_QUOTES, 'UTF-8'); | ||||
|         // return iconv("UTF-8", "ISO-8859-1//TRANSLIT", $this->decode_encoded_utf8($string));
 | ||||
|     } | ||||
| 
 | ||||
|     private function decode_encoded_utf8($string){ | ||||
|  | ||||
| @ -362,9 +362,14 @@ class StripePaymentDriver extends BaseDriver | ||||
|         $response = null; | ||||
| 
 | ||||
|         try { | ||||
|             $response = $this->stripe | ||||
|                 ->refunds | ||||
|                 ->create(['charge' => $payment->transaction_reference, 'amount' => $this->convertToStripeAmount($amount, $this->client->currency()->precision, $this->client->currency())], $meta); | ||||
|             // $response = $this->stripe
 | ||||
|             //     ->refunds
 | ||||
|             //     ->create(['charge' => $payment->transaction_reference, 'amount' => $this->convertToStripeAmount($amount, $this->client->currency()->precision, $this->client->currency())], $meta);
 | ||||
| 
 | ||||
|             $response = \Stripe\Refund::create([ | ||||
|                 'charge' => $payment->transaction_reference,  | ||||
|                 'amount' => $this->convertToStripeAmount($amount, $this->client->currency()->precision, $this->client->currency()) | ||||
|             ], $meta); | ||||
| 
 | ||||
|             if ($response->status == $response::STATUS_SUCCEEDED) { | ||||
|                 SystemLogger::dispatch(['server_response' => $response, 'data' => request()->all(),], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_STRIPE, $this->client, $this->client->company); | ||||
|  | ||||
| @ -60,12 +60,18 @@ use WePayCommon; | ||||
|           'method' => '1', | ||||
|          */ | ||||
| 
 | ||||
| 		$response = $this->wepay_payment_driver->wepay->request('credit_card/authorize', array( | ||||
| 		    'client_id'          => config('ninja.wepay.client_id'), | ||||
| 		    'client_secret'      => config('ninja.wepay.client_secret'), | ||||
| 		    'credit_card_id'     => (int)$data['credit_card_id'], | ||||
| 		)); | ||||
|         try { | ||||
|              | ||||
|     		$response = $this->wepay_payment_driver->wepay->request('credit_card/authorize', array( | ||||
|     		    'client_id'          => config('ninja.wepay.client_id'), | ||||
|     		    'client_secret'      => config('ninja.wepay.client_secret'), | ||||
|     		    'credit_card_id'     => (int)$data['credit_card_id'], | ||||
|     		)); | ||||
| 
 | ||||
|         } | ||||
|         catch(\Exception $e){ | ||||
|             return $this->wepay_payment_driver->processInternallyFailedPayment($this->wepay_payment_driver, $e); | ||||
|         } | ||||
| 		// display the response
 | ||||
|         // nlog($response);
 | ||||
|          | ||||
| @ -116,11 +122,16 @@ use WePayCommon; | ||||
|         { | ||||
|             nlog("authorize the card first!"); | ||||
| 
 | ||||
|             $response = $this->wepay_payment_driver->wepay->request('credit_card/authorize', array( | ||||
|                 'client_id'          => config('ninja.wepay.client_id'), | ||||
|                 'client_secret'      => config('ninja.wepay.client_secret'), | ||||
|                 'credit_card_id'     => (int)$request->input('credit_card_id'), | ||||
|             )); | ||||
|             try { | ||||
|                 $response = $this->wepay_payment_driver->wepay->request('credit_card/authorize', array( | ||||
|                     'client_id'          => config('ninja.wepay.client_id'), | ||||
|                     'client_secret'      => config('ninja.wepay.client_secret'), | ||||
|                     'credit_card_id'     => (int)$request->input('credit_card_id'), | ||||
|                 )); | ||||
|             } | ||||
|             catch(\Exception $e){ | ||||
|                 return $this->wepay_payment_driver->processInternallyFailedPayment($this->wepay_payment_driver, $e); | ||||
|             } | ||||
| 
 | ||||
|             $credit_card_id = (int)$response->credit_card_id; | ||||
| 
 | ||||
|  | ||||
| @ -161,6 +161,7 @@ use App\Listeners\Payment\PaymentEmailedActivity; | ||||
| use App\Listeners\Payment\PaymentNotification; | ||||
| use App\Listeners\Payment\PaymentRestoredActivity; | ||||
| use App\Listeners\Quote\QuoteApprovedActivity; | ||||
| use App\Listeners\Quote\QuoteApprovedWebhook; | ||||
| use App\Listeners\Quote\QuoteArchivedActivity; | ||||
| use App\Listeners\Quote\QuoteCreatedNotification; | ||||
| use App\Listeners\Quote\QuoteDeletedActivity; | ||||
| @ -385,6 +386,7 @@ class EventServiceProvider extends ServiceProvider | ||||
|         QuoteWasApproved::class => [ | ||||
|             ReachWorkflowSettings::class, | ||||
|             QuoteApprovedActivity::class, | ||||
|             QuoteApprovedWebhook::class, | ||||
|         ], | ||||
|         QuoteWasCreated::class => [ | ||||
|             CreatedQuoteActivity::class, | ||||
|  | ||||
| @ -56,8 +56,10 @@ class ClientContactRepository extends BaseRepository | ||||
| 
 | ||||
|             if (! $update_contact) { | ||||
|                 $update_contact = ClientContactFactory::create($client->company_id, $client->user_id); | ||||
|                 $update_contact->client_id = $client->id; | ||||
|             } | ||||
|              | ||||
|             //10-09-2021 - enforce the client->id and remove client_id from fillables
 | ||||
|             $update_contact->client_id = $client->id; | ||||
| 
 | ||||
|             /* We need to set NULL email addresses to blank strings to pass authentication*/ | ||||
|             if(array_key_exists('email', $contact) && is_null($contact['email'])) | ||||
| @ -71,7 +73,9 @@ class ClientContactRepository extends BaseRepository | ||||
|                 $client->company->client_contacts()->where('email', $update_contact->email)->update(['password' => $update_contact->password]); | ||||
|             } | ||||
| 
 | ||||
|             $update_contact->email = trim($contact['email']); | ||||
|             if(array_key_exists('email', $contact)) | ||||
|                 $update_contact->email = trim($contact['email']); | ||||
| 
 | ||||
|             $update_contact->save(); | ||||
|         }); | ||||
| 
 | ||||
| @ -88,5 +92,7 @@ class ClientContactRepository extends BaseRepository | ||||
|             $new_contact->email = ' '; | ||||
|             $new_contact->save(); | ||||
|         } | ||||
| 
 | ||||
|         $client = null; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -168,7 +168,9 @@ class InvoiceMigrationRepository extends BaseRepository | ||||
|             $model->save(); | ||||
|         } | ||||
| 
 | ||||
|         if($data['deleted_at']) | ||||
|         if($data['deleted_at'] == '0000-00-00 00:00:00.000000') | ||||
|             $model->deleted_at = null; | ||||
|         else if($data['deleted_at']) | ||||
|             $model->delete(); | ||||
| 
 | ||||
|         $model->save(); | ||||
|  | ||||
| @ -63,8 +63,8 @@ class TriggeredActions extends AbstractService | ||||
|     private function sendEmail() | ||||
|     { | ||||
| 
 | ||||
|         //$reminder_template = $this->invoice->calculateTemplate('invoice');
 | ||||
|         $reminder_template = 'payment'; | ||||
|         $reminder_template = $this->invoice->calculateTemplate('invoice'); | ||||
|         //$reminder_template = 'payment';
 | ||||
| 
 | ||||
|         $this->invoice->invitations->load('contact.client.country', 'invoice.client.country', 'invoice.company')->each(function ($invitation) use ($reminder_template) { | ||||
|             EmailEntity::dispatch($invitation, $this->invoice->company, $reminder_template); | ||||
|  | ||||
| @ -83,6 +83,7 @@ class UpdateInvoicePayment | ||||
|                 ->updatePaidToDate($paid_amount) | ||||
|                 ->updateStatus() | ||||
|                 ->deletePdf() | ||||
|                 ->workFlow() | ||||
|                 ->save(); | ||||
| 
 | ||||
|             event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); | ||||
|  | ||||
| @ -38,6 +38,7 @@ use App\Utils\Traits\MakesHash; | ||||
| use App\Utils\Traits\SubscriptionHooker; | ||||
| use Carbon\Carbon; | ||||
| use GuzzleHttp\RequestOptions; | ||||
| use Illuminate\Contracts\Container\BindingResolutionException; | ||||
| 
 | ||||
| class SubscriptionService | ||||
| { | ||||
| @ -950,7 +951,12 @@ class SubscriptionService | ||||
|         return redirect($default_redirect); | ||||
|     } | ||||
| 
 | ||||
|     public function planPaid($invoice) | ||||
|     /** | ||||
|      * @param Invoice $invoice  | ||||
|      * @return true  | ||||
|      * @throws BindingResolutionException  | ||||
|      */ | ||||
|     public function planPaid(Invoice $invoice) | ||||
|     { | ||||
|         $recurring_invoice_hashed_id = $invoice->recurring_invoice()->exists() ? $invoice->recurring_invoice->hashed_id : null; | ||||
| 
 | ||||
| @ -959,12 +965,14 @@ class SubscriptionService | ||||
|                 'subscription' => $this->subscription->hashed_id, | ||||
|                 'recurring_invoice' => $recurring_invoice_hashed_id, | ||||
|                 'client' => $invoice->client->hashed_id, | ||||
|                 'contact' => $invoice->client->primary_contact()->first() ? $invoice->client->contacts->first() : false, | ||||
|                 'contact' => $invoice->client->primary_contact()->first() ? $invoice->client->primary_contact()->first(): $invoice->client->contacts->first(), | ||||
|                 'invoice' => $invoice->hashed_id, | ||||
|             ]; | ||||
| 
 | ||||
|         $response = $this->triggerWebhook($context); | ||||
| 
 | ||||
|         nlog($response); | ||||
|          | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -184,8 +184,10 @@ class HtmlEngine | ||||
|         $data['$invoice.subtotal'] = &$data['$subtotal']; | ||||
| 
 | ||||
|         if ($this->entity->partial > 0) { | ||||
|             $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', 'label' => ctrans('texts.balance_due')]; | ||||
|             $data['$balance_due_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.balance_due')]; | ||||
|             $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', 'label' => ctrans('texts.partial_due')]; | ||||
|             $data['$balance_due_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.partial_due')]; | ||||
|             $data['$due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')]; | ||||
| 
 | ||||
|         } else { | ||||
| 
 | ||||
|             if($this->entity->status_id == 1){ | ||||
| @ -226,6 +228,8 @@ class HtmlEngine | ||||
|         $data['$invoice.taxes'] = &$data['$taxes']; | ||||
| 
 | ||||
|         $data['$user.name'] = ['value' => $this->entity->user->present()->name(), 'label' => ctrans('texts.name')]; | ||||
|         $data['$user.first_name'] = ['value' => $this->entity->user->first_name, 'label' => ctrans('texts.first_name')]; | ||||
|         $data['$user.last_name'] = ['value' => $this->entity->user->last_name, 'label' => ctrans('texts.last_name')]; | ||||
|         $data['$user_iban'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company1')]; | ||||
|         $data['$invoice.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')]; | ||||
|         $data['$invoice.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')]; | ||||
|  | ||||
| @ -40,7 +40,7 @@ | ||||
|         "coconutcraig/laravel-postmark": "^2.10", | ||||
|         "codedge/laravel-selfupdater": "^3.2", | ||||
|         "composer/composer": "^2", | ||||
|         "doctrine/dbal": "^2.10", | ||||
|         "doctrine/dbal": "^3.0", | ||||
|         "eway/eway-rapid-php": "^1.3", | ||||
|         "fakerphp/faker": "^1.14", | ||||
|         "fideloper/proxy": "^4.2", | ||||
|  | ||||
							
								
								
									
										722
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										722
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -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.7', | ||||
|     'app_tag' => '5.3.7', | ||||
|     'app_version' => '5.3.10', | ||||
|     'app_tag' => '5.3.10', | ||||
|     'minimum_client_version' => '5.0.16', | ||||
|     'terms_version' => '1.0.1', | ||||
|     'api_secret' => env('API_SECRET', ''), | ||||
|  | ||||
							
								
								
									
										6
									
								
								public/flutter_service_worker.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								public/flutter_service_worker.js
									
									
									
									
										vendored
									
									
								
							| @ -3,12 +3,12 @@ const MANIFEST = 'flutter-app-manifest'; | ||||
| const TEMP = 'flutter-temp-cache'; | ||||
| const CACHE_NAME = 'flutter-app-cache'; | ||||
| const RESOURCES = { | ||||
|   "main.dart.js": "a4ce90340b3e610ee073d2f40c0377c1", | ||||
| "version.json": "46d4015fc9abcefe5371cafcf2084173", | ||||
|   "main.dart.js": "66119d574e0c8fb5df06bb459c3a32ad", | ||||
| "version.json": "9ec5e3813adc4bfd8713556c5059e97d", | ||||
| "manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40", | ||||
| "icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35", | ||||
| "icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed", | ||||
| "/": "7fb4e233bcd97d5af44e8e4ed2d9784b", | ||||
| "/": "1b10e39f425ccd530c4dda303ee1bd11", | ||||
| "assets/NOTICES": "9eb7e2eb2888ea5bae5f536720db37cd", | ||||
| "assets/fonts/MaterialIcons-Regular.otf": "4e6447691c9509f7acdbf8a931a85ca1", | ||||
| "assets/AssetManifest.json": "38d9aea341601f3a5c6fa7b5a1216ea5", | ||||
|  | ||||
							
								
								
									
										237740
									
								
								public/main.dart.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										237740
									
								
								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
											
										
									
								
							
							
								
								
									
										238754
									
								
								public/main.foss.dart.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										238754
									
								
								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
											
										
									
								
							
							
								
								
									
										241853
									
								
								public/main.last.dart.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										241853
									
								
								public/main.last.dart.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										238415
									
								
								public/main.next.dart.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										238415
									
								
								public/main.next.dart.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										14945
									
								
								public/main.profile.dart.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14945
									
								
								public/main.profile.dart.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										237420
									
								
								public/main.wasm.dart.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										237420
									
								
								public/main.wasm.dart.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -1 +1 @@ | ||||
| {"app_name":"invoiceninja_flutter","version":"5.0.58","build_number":"58"} | ||||
| {"app_name":"invoiceninja_flutter","version":"5.0.59","build_number":"59"} | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -36,7 +36,8 @@ | ||||
|     } | ||||
| 
 | ||||
|     .company-logo { | ||||
|         max-width: 55%; | ||||
|         height: 100%; | ||||
|         padding-right: 120px; | ||||
|     } | ||||
| 
 | ||||
|     #company-details, | ||||
| @ -251,6 +252,10 @@ | ||||
|     [data-ref*=".line_total-td"] { | ||||
|         white-space: nowrap; | ||||
|     } | ||||
| 
 | ||||
|     .logo-container { | ||||
|         max-height: 160px; | ||||
|     } | ||||
|      | ||||
|     /** Useful snippets, uncomment to enable. **/ | ||||
| 
 | ||||
| @ -288,7 +293,9 @@ | ||||
|     <tr> | ||||
|         <td> | ||||
|             <div class="header-wrapper" id="header"> | ||||
|                 <img class="company-logo" src="$company.logo" alt="$company.name logo"/> | ||||
|                 <div class="logo-container"> | ||||
|                     <img class="company-logo" src="$company.logo" alt="$company.name logo"/> | ||||
|                 </div> | ||||
|                 <div id="company-details"></div> | ||||
|                 <div id="company-address"></div> | ||||
|             </div> | ||||
|  | ||||
| @ -15,7 +15,7 @@ input:checked ~ .dot { | ||||
|   background-color: #48bb78;
 | ||||
| } | ||||
| </style> | ||||
| <div class="container flex flex-wrap pt-4 pb-10 m-auto mt-6 md:mt-15 lg:px-12 xl:px-16" x-data="{show: true}"> | ||||
| <div id="datadiv" class="container flex flex-wrap pt-2 pb-10 m-auto mt-2 md:mt-5 lg:px-16 xl:px-16" x-data="{show: true}"> | ||||
|     <div class="w-full px-0 lg:px-4"> | ||||
|       <h2 class="px-12 text-base font-bold text-center md:text-2xl text-blue-700"> | ||||
|         Choose your plan | ||||
| @ -61,14 +61,28 @@ input:checked ~ .dot { | ||||
|               <p class="text-xs text-center uppercase text-white"> | ||||
|                 monthly | ||||
|               </p> | ||||
| 
 | ||||
|               <div class="py-2 text-sm my-3 text-white">Unlimited clients, invoices, quotes, recurring invoices</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">10 professional invoice & quote template designs</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">Remove "Created by Invoice Ninja" from invoices</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">Enable emails to be sent via Gmail</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">Integrate with Zapier, Integromat or API</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">+ Much more!</div> | ||||
| 
 | ||||
|             </div> | ||||
| 
 | ||||
|             <div class="flex flex-col items-center justify-center w-full h-full py-6 rounded-b-lg bg-blue-700"> | ||||
|               <p class="text-xl text-white"> | ||||
|                 Sign up! | ||||
|                 Single User | ||||
|               </p> | ||||
|               <a type="button" class="w-5/6 py-2 mt-2 font-semibold text-center uppercase bg-white border border-transparent rounded text-blue-500" href="https://invoiceninja.invoicing.co/client/subscriptions/WJxbojagwO/purchase"> | ||||
|               <button id="handleProMonthlyClick" class="w-5/6 py-2 mt-2 font-semibold text-center uppercase bg-white border border-transparent rounded text-blue-500"> | ||||
|                 Purchase | ||||
|               </a> | ||||
|               </button> | ||||
|             </div> | ||||
|           </label> | ||||
|         </div> | ||||
| @ -78,33 +92,46 @@ input:checked ~ .dot { | ||||
|           <label class="flex flex-col rounded-lg shadow-lg relative cursor-pointer hover:shadow-2xl"> | ||||
|             <div class="w-full px-4 py-8 rounded-t-lg bg-blue-500"> | ||||
|               <h3 class="mx-auto text-base font-semibold text-center underline text-white group-hover:text-white"> | ||||
|                 Enterprise (1-2 Users) | ||||
|                 Enterprise Plan | ||||
|               </h3> | ||||
|               <p class="text-5xl font-bold text-center text-white"> | ||||
|               <p class="text-5xl font-bold text-center text-white" id="m_plan_price"> | ||||
|                 $14 | ||||
|               </p> | ||||
|               <p class="text-xs text-center uppercase text-white"> | ||||
|                 monthly | ||||
|               </p> | ||||
| 
 | ||||
|               <div class="py-2 text-sm my-3 text-white">Multiple users and advanced permissions per user</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">Attach documents to emails & client side portal!</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">Branded client portal: "https://billing.yourcompany.com"</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">Custom background for invoices & quotes</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">Priority support</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">+ Much more!</div> | ||||
| 
 | ||||
|             </div> | ||||
|             <div class="flex flex-col items-center justify-center w-full h-full py-6 rounded-b-lg bg-blue-700"> | ||||
|               <p class="text-xl text-white"> | ||||
|                 Sign up! | ||||
|                 <select id="users_monthly" class="bg-white text-black appearance-none border-none inline-block py-0 pl-3 pr-2 rounded leading-tight w-full"> | ||||
|                   <option value="7LDdwRb1YK" selected>1-2 Users</option> | ||||
|                   <option value="MVyb8mdvAZ">3-5 Users</option> | ||||
|                   <option value="WpmbkR5azJ">6-10 Users</option> | ||||
|                   <option value="k8mepY2aMy">11-20 Users</option> | ||||
|                 </select> | ||||
|               </p> | ||||
|               <a type="button" class="w-5/6 py-2 mt-2 font-semibold text-center uppercase bg-white border border-transparent rounded text-blue-500" href="https://invoiceninja.invoicing.co/client/subscriptions/7LDdwRb1YK/purchase"> | ||||
|               <button id="handleMonthlyClick" class="w-5/6 py-2 mt-2 font-semibold text-center uppercase bg-white border border-transparent rounded text-blue-500"> | ||||
|                 Purchase | ||||
|               </a> | ||||
|               </button> | ||||
|             </div> | ||||
|           </label> | ||||
|         </div> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|       </div> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|       <!-- Annual Plans --> | ||||
|       <div class="flex flex-wrap items-center justify-center py-4 pt-0" x-show=" !show "> | ||||
|         <div class="w-full p-4 md:w-1/2 lg:w-1/2"> | ||||
| @ -119,6 +146,19 @@ input:checked ~ .dot { | ||||
|               <p class="text-xs text-center uppercase text-white"> | ||||
|                 yearly | ||||
|               </p> | ||||
| 
 | ||||
|               <div class="py-2 text-sm my-3 text-white">Unlimited clients, invoices, quotes, recurring invoices</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">10 professional invoice & quote template designs</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">Remove "Created by Invoice Ninja" from invoices</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">Enable emails to be sent via Gmail</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">Integrate with Zapier, Integromat or API</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">+ Much more!</div> | ||||
| 
 | ||||
|             </div> | ||||
|             <div | ||||
|               class="flex flex-col items-center justify-center w-full h-full py-6 rounded-b-lg bg-blue-700" | ||||
| @ -126,9 +166,9 @@ input:checked ~ .dot { | ||||
|               <p class="text-xl text-white"> | ||||
|                 Buy 10 months get 2 free! | ||||
|               </p> | ||||
|                 <a type="button" class="w-5/6 py-2 mt-2 font-semibold text-center uppercase bg-white border border-transparent rounded text-blue-500" href="https://invoiceninja.invoicing.co/client/subscriptions/q9wdL9wejP/purchase"> | ||||
|                 <button id="handleProYearlyClick" class="w-5/6 py-2 mt-2 font-semibold text-center uppercase bg-white border border-transparent rounded text-blue-500"> | ||||
|                 Purchase | ||||
|               </a> | ||||
|               </button> | ||||
|             </div> | ||||
|           </label> | ||||
|         </div> | ||||
| @ -137,24 +177,42 @@ input:checked ~ .dot { | ||||
|           <label class="flex flex-col rounded-lg shadow-lg relative cursor-pointer hover:shadow-2xl"> | ||||
|             <div class="w-full px-4 py-8 rounded-t-lg bg-blue-500"> | ||||
|               <h3 class="mx-auto text-base font-semibold text-center underline text-white group-hover:text-white"> | ||||
|                 Enterprise (1-2 Users) | ||||
|                 Enterprise Plan | ||||
|               </h3> | ||||
|               <p class="text-5xl font-bold text-center text-white"> | ||||
|               <p class="text-5xl font-bold text-center text-white" id="y_plan_price"> | ||||
|                 $140 | ||||
|               </p> | ||||
|               <p class="text-xs text-center uppercase text-white"> | ||||
|                 yearly | ||||
|               </p> | ||||
| 
 | ||||
|               <div class="py-2 text-sm my-3 text-white">Multiple users and advanced permissions per user</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">Attach documents to emails & client side portal!</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">Branded client portal: "https://billing.yourcompany.com"</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">Custom background for invoices & quotes</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">Priority support</div> | ||||
|               <hr> | ||||
|               <div class="py-2 text-sm my-3 text-white">+ Much more!</div> | ||||
| 
 | ||||
|             </div> | ||||
|             <div | ||||
|               class="flex flex-col items-center justify-center w-full h-full py-6 rounded-b-lg bg-blue-700" | ||||
|             > | ||||
|               <p class="text-xl text-white"> | ||||
|                 Buy 10 months get 2 free! | ||||
|                 <select id="users_yearly" class="bg-white text-black appearance-none border-none inline-block py-0 pl-3 pr-2 rounded leading-tight w-full"> | ||||
|                   <option value="LYqaQWldnj" selected>1-2 Users</option> | ||||
|                   <option value="kQBeX6mbyK">3-5 Users</option> | ||||
|                   <option value="GELe32Qd69">6-10 Users</option> | ||||
|                   <option value="MVyb86oevA">11-20 Users</option> | ||||
|                 </select> | ||||
|               </p> | ||||
|                 <a type="button" class="w-5/6 py-2 mt-2 font-semibold text-center uppercase bg-white border border-transparent rounded text-blue-500" href="https://invoiceninja.invoicing.co/client/subscriptions/LYqaQWldnj/purchase"> | ||||
|               <button id="handleYearlyClick" class="w-5/6 py-2 mt-2 font-semibold text-center uppercase bg-white border border-transparent rounded text-blue-500" > | ||||
|                 Purchase | ||||
|               </a> | ||||
|               </button> | ||||
|             </div> | ||||
|           </label> | ||||
|         </div> | ||||
| @ -167,5 +225,68 @@ input:checked ~ .dot { | ||||
| 
 | ||||
| @push('footer') | ||||
| 
 | ||||
| <script type="text/javascript"> | ||||
| 
 | ||||
| var users_yearly = 'LYqaQWldnj'; | ||||
| var users_monthly = '7LDdwRb1YK'; | ||||
| 
 | ||||
| document.getElementById('users_yearly').options[0].selected = true; | ||||
| document.getElementById('users_monthly').options[0].selected = true; | ||||
| 
 | ||||
| document.getElementById("toggleB").addEventListener('change', function() { | ||||
| 
 | ||||
|   document.getElementById('users_yearly').options[0].selected = true; | ||||
|   document.getElementById('users_monthly').options[0].selected = true; | ||||
|   document.getElementById('y_plan_price').innerHTML = price_map.get('LYqaQWldnj'); | ||||
|   document.getElementById('m_plan_price').innerHTML = price_map.get('7LDdwRb1YK'); | ||||
| 
 | ||||
|   users_yearly = 'LYqaQWldnj'; | ||||
|   users_monthly = '7LDdwRb1YK'; | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| document.getElementById('users_yearly').addEventListener('change', function() { | ||||
|   users_yearly = this.value; | ||||
|   document.getElementById('y_plan_price').innerHTML = price_map.get(this.value); | ||||
| }); | ||||
| 
 | ||||
| document.getElementById('users_monthly').addEventListener('change', function() { | ||||
|   users_monthly = this.value; | ||||
|   document.getElementById('m_plan_price').innerHTML = price_map.get(this.value); | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| document.getElementById('handleYearlyClick').addEventListener('click', function() { | ||||
|   document.getElementById("toggleB").checked = false; | ||||
|   location.href = 'https://invoiceninja.invoicing.co/client/subscriptions/' + users_yearly + '/purchase'; | ||||
| }); | ||||
| 
 | ||||
| document.getElementById('handleMonthlyClick').addEventListener('click', function() { | ||||
|   document.getElementById("toggleB").checked = false; | ||||
|   location.href = 'https://invoiceninja.invoicing.co/client/subscriptions/' + users_monthly + '/purchase'; | ||||
| }); | ||||
| 
 | ||||
| document.getElementById('handleProMonthlyClick').addEventListener('click', function() { | ||||
|   document.getElementById("toggleB").checked = false; | ||||
|   location.href = 'https://invoiceninja.invoicing.co/client/subscriptions/WJxbojagwO/purchase'; | ||||
| }); | ||||
| 
 | ||||
| document.getElementById('handleProYearlyClick').addEventListener('click', function() { | ||||
|   document.getElementById("toggleB").checked = false; | ||||
|   location.href = 'https://invoiceninja.invoicing.co/client/subscriptions/q9wdL9wejP/purchase'; | ||||
| }); | ||||
| const price_map = new Map(); | ||||
| //monthly
 | ||||
| price_map.set('7LDdwRb1YK', '$14'); | ||||
| price_map.set('MVyb8mdvAZ', '$26'); | ||||
| price_map.set('WpmbkR5azJ', '$36'); | ||||
| price_map.set('k8mepY2aMy', '$44'); | ||||
| //yearly
 | ||||
| price_map.set('LYqaQWldnj', '$140'); | ||||
| price_map.set('kQBeX6mbyK', '$260'); | ||||
| price_map.set('GELe32Qd69', '$360'); | ||||
| price_map.set('MVyb86oevA', '$440'); | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| @endpush | ||||
|  | ||||
| @ -51,6 +51,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a | ||||
|     Route::resource('companies', 'CompanyController'); // name = (companies. index / create / show / update / destroy / edit
 | ||||
|      | ||||
|     Route::put('companies/{company}/upload', 'CompanyController@upload'); | ||||
|     Route::post('companies/{company}/default', 'CompanyController@default'); | ||||
| 
 | ||||
|     Route::get('company_ledger', 'CompanyLedgerController@index')->name('company_ledger.index'); | ||||
| 
 | ||||
|  | ||||
| @ -100,7 +100,7 @@ Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'clie | ||||
|     Route::get('quote/{invitation_key}/download_pdf', 'QuoteController@downloadPdf')->name('quote.download_invitation_key'); | ||||
|     Route::get('credit/{invitation_key}/download_pdf', 'CreditController@downloadPdf')->name('credit.download_invitation_key'); | ||||
|     Route::get('{entity}/{invitation_key}/download', 'ClientPortal\InvitationController@routerForDownload'); | ||||
|     Route::get('{entity}/{client_hash}/{invitation_key}', 'ClientPortal\InvitationController@routerForIframe')->name('invoice.client_hash_and_invitation_key'); //should never need this
 | ||||
|     // Route::get('{entity}/{client_hash}/{invitation_key}', 'ClientPortal\InvitationController@routerForIframe')->name('invoice.client_hash_and_invitation_key'); //should never need this
 | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
|  | ||||
| @ -208,6 +208,7 @@ trait MockAccountData | ||||
|         $this->cu = CompanyUserFactory::create($user->id, $this->company->id, $this->account->id); | ||||
|         $this->cu->is_owner = true; | ||||
|         $this->cu->is_admin = true; | ||||
|         $this->cu->is_locked = false; | ||||
|         $this->cu->save(); | ||||
| 
 | ||||
|         $this->token = \Illuminate\Support\Str::random(64); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user