mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-11-02 10:49:42 -05: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