mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge branch 'v5-develop' of https://github.com/turbo124/invoiceninja into v5-develop
This commit is contained in:
commit
8d490200b6
10
README.md
10
README.md
@ -7,15 +7,7 @@
|
||||
|
||||
[](https://www.codacy.com/app/turbo124/invoiceninja?utm_source=github.com&utm_medium=referral&utm_content=invoiceninja/invoiceninja&utm_campaign=Badge_Grade)
|
||||
|
||||
# Invoice Ninja version 5.1 RC2!
|
||||
|
||||
Invoice Ninja version 5.1 has now reached Release Candidate 2!
|
||||
|
||||
What does this mean exactly? We consider this version _almost_ stable. There may be some remaining small issues which we would love to get feedback on. We would really appreciate the community booting up this version and attempting the migration from their Invoice Ninja V4 application and inspect the migrated data.
|
||||
|
||||
We'd also like feedback on any issues that you can see, and help us nail down the few remaining issues before Version 5 graduates to Stable Gold Release.
|
||||
|
||||
Please note we do not consider this version ready for production use, please stick with your V4 installation for your production clients!
|
||||
# Invoice Ninja version 5!
|
||||
|
||||
## Quick Start
|
||||
|
||||
|
@ -1 +1 @@
|
||||
5.1.24
|
||||
5.1.29
|
@ -165,6 +165,7 @@ class DemoMode extends Command
|
||||
'account_id' => $account->id,
|
||||
'email' => 'small@example.com',
|
||||
'confirmation_code' => $this->createDbHash(config('database.default')),
|
||||
'email_verified_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -199,6 +200,7 @@ class DemoMode extends Command
|
||||
'password' => Hash::make('Password0'),
|
||||
'account_id' => $account->id,
|
||||
'confirmation_code' => $this->createDbHash(config('database.default')),
|
||||
'email_verified_at' => now(),
|
||||
]);
|
||||
|
||||
$company_token = new CompanyToken;
|
||||
|
@ -57,24 +57,17 @@ class Kernel extends ConsoleKernel
|
||||
$schedule->job(new BillingSubscriptionCron)->daily()->withoutOverlapping();
|
||||
|
||||
$schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping();
|
||||
|
||||
$schedule->job(new SchedulerCheck)->everyFiveMinutes();
|
||||
|
||||
/* Run hosted specific jobs */
|
||||
if (Ninja::isHosted()) {
|
||||
|
||||
$schedule->job(new AdjustEmailQuota())->daily()->withoutOverlapping();
|
||||
$schedule->job(new SendFailedEmails())->daily()->withoutOverlapping();
|
||||
$schedule->job(new AdjustEmailQuota)->daily()->withoutOverlapping();
|
||||
$schedule->job(new SendFailedEmails)->daily()->withoutOverlapping();
|
||||
|
||||
}
|
||||
/* Run queue's with this*/
|
||||
if (Ninja::isSelfHost()) {
|
||||
|
||||
$schedule->command('queue:work')->everyMinute()->withoutOverlapping();
|
||||
|
||||
//we need to add this as we are seeing cached queues mess up the system on first load.
|
||||
$schedule->command('queue:restart')->everyFiveMinutes()->withoutOverlapping();
|
||||
$schedule->job(new SchedulerCheck)->everyFiveMinutes()->withoutOverlapping();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
47
app/DataMapper/Billing/WebhookConfiguration.php
Normal file
47
app/DataMapper/Billing/WebhookConfiguration.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?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://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\DataMapper\Billing;
|
||||
|
||||
|
||||
class WebhookConfiguration
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $return_url = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $post_purchase_url = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $post_purchase_headers = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $post_purchase_body = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $casts = [
|
||||
'return_url' => 'string',
|
||||
'post_purchase_url' => 'string',
|
||||
'post_purchase_headers' => 'array',
|
||||
'post_purchase_body' => 'object',
|
||||
];
|
||||
}
|
@ -108,6 +108,7 @@ class CompanySettings extends BaseSettings
|
||||
public $project_number_counter = 1; //@implemented
|
||||
|
||||
public $shared_invoice_quote_counter = false; //@implemented
|
||||
public $shared_invoice_credit_counter = false; //@implemented
|
||||
public $recurring_number_prefix = 'R'; //@implemented
|
||||
public $reset_counter_frequency_id = '0'; //@implemented
|
||||
public $reset_counter_date = ''; //@implemented
|
||||
@ -262,6 +263,7 @@ class CompanySettings extends BaseSettings
|
||||
public $hide_empty_columns_on_pdf = false;
|
||||
|
||||
public static $casts = [
|
||||
'shared_invoice_credit_counter' => 'bool',
|
||||
'reply_to_name' => 'string',
|
||||
'hide_empty_columns_on_pdf' => 'bool',
|
||||
'enable_reminder_endless' => 'bool',
|
||||
|
@ -51,7 +51,7 @@ class InvoiceItem
|
||||
|
||||
public $custom_value4 = '';
|
||||
|
||||
public $type_id = '1'; //1 = product, 2 = service, 3 unpaid gateway fee, 4 paid gateway fee, 5 late fee
|
||||
public $type_id = '1'; //1 = product, 2 = service, 3 unpaid gateway fee, 4 paid gateway fee, 5 late fee, 6 promo code
|
||||
|
||||
public static $casts = [
|
||||
'type_id' => 'string',
|
||||
|
@ -35,7 +35,7 @@ class CompanyFactory
|
||||
$company->custom_fields = (object) [];
|
||||
$company->subdomain = '';
|
||||
$company->enabled_modules = config('ninja.enabled_modules'); //32767;//8191; //4095
|
||||
$company->default_password_timeout = 30;
|
||||
$company->default_password_timeout = 30 * 60000;
|
||||
|
||||
return $company;
|
||||
}
|
||||
|
@ -142,11 +142,11 @@ class ActivityController extends BaseController
|
||||
$pdf = $this->makePdf(null, null, $backup->html_backup);
|
||||
|
||||
if (isset($activity->invoice_id)) {
|
||||
$filename = $activity->invoice->number.'.pdf';
|
||||
$filename = $activity->invoice->numberFormatter().'.pdf';
|
||||
} elseif (isset($activity->quote_id)) {
|
||||
$filename = $activity->quote->number.'.pdf';
|
||||
$filename = $activity->quote->numberFormatter().'.pdf';
|
||||
} elseif (isset($activity->credit_id)) {
|
||||
$filename = $activity->credit->number.'.pdf';
|
||||
$filename = $activity->credit->numberFormatter().'.pdf';
|
||||
} else {
|
||||
$filename = 'backup.pdf';
|
||||
}
|
||||
|
@ -28,6 +28,9 @@ use Google_Client;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
|
||||
class LoginController extends BaseController
|
||||
@ -159,19 +162,48 @@ class LoginController extends BaseController
|
||||
}
|
||||
|
||||
if ($this->attemptLogin($request)) {
|
||||
|
||||
LightLogs::create(new LoginSuccess())
|
||||
->increment()
|
||||
->batch();
|
||||
|
||||
$user = $this->guard()->user();
|
||||
|
||||
//if user has 2fa enabled - lets check this now:
|
||||
|
||||
if($user->google_2fa_secret && $request->has('one_time_password'))
|
||||
{
|
||||
$google2fa = new Google2FA();
|
||||
|
||||
if(strlen($request->input('one_time_password')) == 0 || !$google2fa->verifyKey(decrypt($user->google_2fa_secret), $request->input('one_time_password')))
|
||||
{
|
||||
return response()
|
||||
->json(['message' => ctrans('texts.invalid_one_time_password')], 401)
|
||||
->header('X-App-Version', config('ninja.app_version'))
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
}
|
||||
|
||||
}
|
||||
elseif($user->google_2fa_secret && !$request->has('one_time_password')) {
|
||||
|
||||
return response()
|
||||
->json(['message' => ctrans('texts.invalid_one_time_password')], 401)
|
||||
->header('X-App-Version', config('ninja.app_version'))
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
}
|
||||
|
||||
$user->setCompany($user->account->default_company);
|
||||
$timeout = auth()->user()->company()->default_password_timeout;
|
||||
|
||||
Cache::put(auth()->user()->hashed_id.'_logged_in', Str::random(64), $timeout);
|
||||
|
||||
$cu = CompanyUser::query()
|
||||
->where('user_id', auth()->user()->id);
|
||||
|
||||
return $this->listResponse($cu);
|
||||
|
||||
} else {
|
||||
|
||||
LightLogs::create(new LoginFailure())
|
||||
->increment()
|
||||
->batch();
|
||||
@ -182,6 +214,7 @@ class LoginController extends BaseController
|
||||
->json(['message' => ctrans('texts.invalid_credentials')], 401)
|
||||
->header('X-App-Version', config('ninja.app_version'))
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,12 +302,14 @@ class LoginController extends BaseController
|
||||
$user = $google->getTokenResponse(request()->input('id_token'));
|
||||
|
||||
if (is_array($user)) {
|
||||
|
||||
$query = [
|
||||
'oauth_user_id' => $google->harvestSubField($user),
|
||||
'oauth_provider_id'=> 'google',
|
||||
];
|
||||
|
||||
if ($existing_user = MultiDB::hasUser($query)) {
|
||||
|
||||
Auth::login($existing_user, true);
|
||||
$existing_user->setCompany($existing_user->account->default_company);
|
||||
|
||||
@ -282,38 +317,40 @@ class LoginController extends BaseController
|
||||
->where('user_id', auth()->user()->id);
|
||||
|
||||
return $this->listResponse($cu);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
|
||||
$client = new Google_Client();
|
||||
$client->setClientId(config('ninja.auth.google.client_id'));
|
||||
$client->setClientSecret(config('ninja.auth.google.client_secret'));
|
||||
$client->setRedirectUri(config('ninja.app_url'));
|
||||
// we are no longer accessing the permissions for gmail - email permissions here
|
||||
|
||||
$token = false;
|
||||
// $client = new Google_Client();
|
||||
// $client->setClientId(config('ninja.auth.google.client_id'));
|
||||
// $client->setClientSecret(config('ninja.auth.google.client_secret'));
|
||||
// $client->setRedirectUri(config('ninja.app_url'));
|
||||
|
||||
try{
|
||||
$token = $client->authenticate(request()->input('server_auth_code'));
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
// $token = false;
|
||||
|
||||
return response()
|
||||
->json(['message' => ctrans('texts.invalid_credentials')], 401)
|
||||
->header('X-App-Version', config('ninja.app_version'))
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
// try{
|
||||
// $token = $client->authenticate(request()->input('server_auth_code'));
|
||||
// }
|
||||
// catch(\Exception $e) {
|
||||
|
||||
}
|
||||
// return response()
|
||||
// ->json(['message' => ctrans('texts.invalid_credentials')], 401)
|
||||
// ->header('X-App-Version', config('ninja.app_version'))
|
||||
// ->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
|
||||
$refresh_token = '';
|
||||
// }
|
||||
|
||||
if (array_key_exists('refresh_token', $token)) {
|
||||
$refresh_token = $token['refresh_token'];
|
||||
}
|
||||
// $refresh_token = '';
|
||||
|
||||
//$access_token = $token['access_token'];
|
||||
// if (array_key_exists('refresh_token', $token)) {
|
||||
// $refresh_token = $token['refresh_token'];
|
||||
// }
|
||||
|
||||
|
||||
$name = OAuth::splitName($google->harvestName($user));
|
||||
|
||||
$new_account = [
|
||||
|
@ -0,0 +1,30 @@
|
||||
<?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://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\ClientPortal;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\BillingSubscription;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class BillingSubscriptionPurchaseController extends Controller
|
||||
{
|
||||
public function index(BillingSubscription $billing_subscription)
|
||||
{
|
||||
return view('billing-portal.purchase', [
|
||||
'billing_subscription' => $billing_subscription,
|
||||
'hash' => Str::uuid()->toString(),
|
||||
]);
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@ use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
|
||||
@ -237,11 +238,18 @@ class PaymentController extends Controller
|
||||
->get();
|
||||
}
|
||||
|
||||
$hash_data = ['invoices' => $payable_invoices->toArray(), 'credits' => $credit_totals];
|
||||
|
||||
if ($request->query('hash')) {
|
||||
$hash_data['billing_context'] = Cache::get($request->query('hash'));
|
||||
}
|
||||
|
||||
$payment_hash = new PaymentHash;
|
||||
$payment_hash->hash = Str::random(128);
|
||||
$payment_hash->data = ['invoices' => $payable_invoices->toArray(), 'credits' => $credit_totals];
|
||||
$payment_hash->data = $hash_data;
|
||||
$payment_hash->fee_total = $fee_totals;
|
||||
$payment_hash->fee_invoice_id = $first_invoice->id;
|
||||
|
||||
$payment_hash->save();
|
||||
|
||||
$totals = [
|
||||
|
@ -21,9 +21,9 @@ use Illuminate\Http\Request;
|
||||
class ConnectedAccountController extends BaseController
|
||||
{
|
||||
|
||||
protected $entity_type = CompanyUser::class;
|
||||
protected $entity_type = User::class;
|
||||
|
||||
protected $entity_transformer = CompanyUserTransformer::class;
|
||||
protected $entity_transformer = UserTransformer::class;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@ -89,23 +89,8 @@ class ConnectedAccountController extends BaseController
|
||||
|
||||
$user = $google->getTokenResponse(request()->input('id_token'));
|
||||
|
||||
if (is_array($user)) {
|
||||
|
||||
$query = [
|
||||
'oauth_user_id' => $google->harvestSubField($user),
|
||||
'oauth_provider_id'=> 'google',
|
||||
];
|
||||
|
||||
/* Cannot allow duplicates! */
|
||||
if ($existing_user = MultiDB::hasUser($query)) {
|
||||
return response()
|
||||
->json(['message' => 'User already exists in system.'], 401)
|
||||
->header('X-App-Version', config('ninja.app_version'))
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
|
||||
$client = new Google_Client();
|
||||
$client->setClientId(config('ninja.auth.google.client_id'));
|
||||
$client->setClientSecret(config('ninja.auth.google.client_secret'));
|
||||
@ -118,7 +103,6 @@ class ConnectedAccountController extends BaseController
|
||||
$refresh_token = $token['refresh_token'];
|
||||
}
|
||||
|
||||
|
||||
$connected_account = [
|
||||
'password' => '',
|
||||
'email' => $google->harvestEmail($user),
|
||||
@ -136,7 +120,8 @@ class ConnectedAccountController extends BaseController
|
||||
//$ct = CompanyUser::whereUserId(auth()->user()->id);
|
||||
//return $this->listResponse($ct);
|
||||
|
||||
return $this->listResponse(auth()->user());
|
||||
return $this->itemResponse(auth()->user());
|
||||
// return $this->listResponse(auth()->user());
|
||||
}
|
||||
|
||||
return response()
|
||||
|
@ -18,6 +18,7 @@ use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Services\PdfMaker\Design;
|
||||
use App\Services\PdfMaker\PdfMaker;
|
||||
use App\Utils\HostedPDF\NinjaPdf;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\PhantomJS\Phantom;
|
||||
@ -133,6 +134,10 @@ class PreviewController extends BaseController
|
||||
if (config('ninja.phantomjs_pdf_generation')) {
|
||||
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
|
||||
}
|
||||
|
||||
if(config('ninja.invoiceninja_hosted_pdf_generation')){
|
||||
return (new NinjaPdf())->build($maker->getCompiledHTML(true));
|
||||
}
|
||||
|
||||
//else
|
||||
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company());
|
||||
@ -216,6 +221,10 @@ class PreviewController extends BaseController
|
||||
if (config('ninja.phantomjs_pdf_generation')) {
|
||||
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
|
||||
}
|
||||
|
||||
if(config('ninja.invoiceninja_hosted_pdf_generation')){
|
||||
return (new NinjaPdf())->build($maker->getCompiledHTML(true));
|
||||
}
|
||||
|
||||
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company());
|
||||
|
||||
|
@ -47,17 +47,28 @@ class TwoFactorController extends BaseController
|
||||
|
||||
public function enableTwoFactor()
|
||||
{
|
||||
$google2fa = new Google2FA();
|
||||
|
||||
$user = auth()->user();
|
||||
$secret = request()->input('secret');
|
||||
$oneTimePassword = request()->input('one_time_password');
|
||||
|
||||
if (! $secret || ! \Google2FA::verifyKey($secret, $oneTimePassword)) {
|
||||
return response()->json('message' > ctrans('texts.invalid_one_time_password'));
|
||||
} elseif (! $user->google_2fa_secret && $user->phone && $user->confirmed) {
|
||||
$user->google_2fa_secret = encrypt($secret);
|
||||
$user->save();
|
||||
}
|
||||
if($google2fa->verifyKey($secret, $oneTimePassword) && $user->phone && $user->email_verified_at){
|
||||
|
||||
return response()->json(['message' => ctrans('texts.enabled_two_factor')], 200);
|
||||
$user->google_2fa_secret = encrypt($secret);
|
||||
|
||||
$user->save();
|
||||
|
||||
return response()->json(['message' => ctrans('texts.enabled_two_factor')], 200);
|
||||
|
||||
} elseif (! $secret || ! $google2fa->verifyKey($secret, $oneTimePassword)) {
|
||||
|
||||
return response()->json(['message' => ctrans('texts.invalid_one_time_password')], 400);
|
||||
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'No phone record or user is not confirmed'], 400);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
171
app/Http/Livewire/BillingPortalPurchase.php
Normal file
171
app/Http/Livewire/BillingPortalPurchase.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?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://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\Factory\ClientFactory;
|
||||
use App\Models\ClientContact;
|
||||
use App\Repositories\ClientContactRepository;
|
||||
use App\Repositories\ClientRepository;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Livewire\Component;
|
||||
|
||||
class BillingPortalPurchase extends Component
|
||||
{
|
||||
public $hash;
|
||||
|
||||
public $heading_text = 'Log in';
|
||||
|
||||
public $email;
|
||||
|
||||
public $password;
|
||||
|
||||
public $billing_subscription;
|
||||
|
||||
public $contact;
|
||||
|
||||
protected $rules = [
|
||||
'email' => ['required', 'email'],
|
||||
];
|
||||
|
||||
public $company_gateway_id;
|
||||
|
||||
public $payment_method_id;
|
||||
|
||||
public $steps = [
|
||||
'passed_email' => false,
|
||||
'existing_user' => false,
|
||||
'fetched_payment_methods' => false,
|
||||
'fetched_client' => false,
|
||||
];
|
||||
|
||||
public $methods = [];
|
||||
|
||||
public $invoice;
|
||||
|
||||
public $coupon;
|
||||
|
||||
public function authenticate()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$contact = ClientContact::where('email', $this->email)->first();
|
||||
|
||||
if ($contact && $this->steps['existing_user'] === false) {
|
||||
return $this->steps['existing_user'] = true;
|
||||
}
|
||||
|
||||
if ($contact && $this->steps['existing_user']) {
|
||||
$attempt = Auth::guard('contact')->attempt(['email' => $this->email, 'password' => $this->password]);
|
||||
|
||||
return $attempt
|
||||
? $this->getPaymentMethods($contact)
|
||||
: session()->flash('message', 'These credentials do not match our records.');
|
||||
}
|
||||
|
||||
$this->steps['existing_user'] = false;
|
||||
|
||||
$contact = $this->createBlankClient();
|
||||
|
||||
if ($contact && $contact instanceof ClientContact) {
|
||||
$this->getPaymentMethods($contact);
|
||||
}
|
||||
}
|
||||
|
||||
protected function createBlankClient()
|
||||
{
|
||||
$company = $this->billing_subscription->company;
|
||||
$user = $this->billing_subscription->user;
|
||||
|
||||
$client_repo = new ClientRepository(new ClientContactRepository());
|
||||
|
||||
$client = $client_repo->save([
|
||||
'name' => 'Client Name',
|
||||
'contacts' => [
|
||||
['email' => $this->email],
|
||||
]
|
||||
], ClientFactory::create($company->id, $user->id));
|
||||
|
||||
return $client->contacts->first();
|
||||
}
|
||||
|
||||
protected function getPaymentMethods(ClientContact $contact): self
|
||||
{
|
||||
$this->steps['fetched_payment_methods'] = true;
|
||||
|
||||
$this->methods = $contact->client->service()->getPaymentMethods(1000);
|
||||
|
||||
$this->heading_text = 'Pick a payment method';
|
||||
|
||||
Auth::guard('contact')->login($contact);
|
||||
|
||||
$this->contact = $contact;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function handleMethodSelectingEvent($company_gateway_id, $gateway_type_id)
|
||||
{
|
||||
$this->company_gateway_id = $company_gateway_id;
|
||||
$this->payment_method_id = $gateway_type_id;
|
||||
|
||||
$this->handleBeforePaymentEvents();
|
||||
}
|
||||
|
||||
public function handleBeforePaymentEvents()
|
||||
{
|
||||
|
||||
//stubs
|
||||
$data = [
|
||||
'client_id' => $this->contact->client->id,
|
||||
'date' => now()->format('Y-m-d'),
|
||||
'invitations' => [[
|
||||
'key' => '',
|
||||
'client_contact_id' => $this->contact->hashed_id,
|
||||
]],
|
||||
'user_input_promo_code' => $this->coupon,
|
||||
'quantity' => 1, // Option to increase quantity
|
||||
];
|
||||
|
||||
$this->invoice = $this->billing_subscription
|
||||
->service()
|
||||
->createInvoice($data)
|
||||
->service()
|
||||
->markSent()
|
||||
->save();
|
||||
|
||||
Cache::put($this->hash, [
|
||||
'email' => $this->email ?? $this->contact->email,
|
||||
'client_id' => $this->contact->client->id,
|
||||
'invoice_id' => $this->invoice->id],
|
||||
now()->addMinutes(60)
|
||||
);
|
||||
|
||||
$this->emit('beforePaymentEventsCompleted');
|
||||
}
|
||||
|
||||
|
||||
//this isn't managed here - this is taken care of in the BS
|
||||
public function applyCouponCode()
|
||||
{
|
||||
dd('Applying coupon code: ' . $this->coupon);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
if ($this->contact instanceof ClientContact) {
|
||||
$this->getPaymentMethods($this->contact);
|
||||
}
|
||||
|
||||
return render('components.livewire.billing-portal-purchase');
|
||||
}
|
||||
}
|
@ -42,7 +42,7 @@ class PasswordProtection
|
||||
if($timeout == 0)
|
||||
$timeout = null;
|
||||
else
|
||||
$timeout = now()->addMinutes($timeout);
|
||||
$timeout = now()->addMinutes($timeout/60000);
|
||||
|
||||
if (Cache::get(auth()->user()->hashed_id.'_logged_in')) {
|
||||
|
||||
@ -67,7 +67,7 @@ class PasswordProtection
|
||||
];
|
||||
|
||||
//If OAuth and user also has a password set - check both
|
||||
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->has_password && Hash::check(auth()->user()->password, $request->header('X-API-PASSWORD'))) {
|
||||
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->has_password && Hash::check(auth()->user()->password, $request->header('X-API-PASSWORD'))) {
|
||||
|
||||
Cache::add(auth()->user()->hashed_id.'_logged_in', Str::random(64), $timeout);
|
||||
return $next($request);
|
||||
|
@ -54,7 +54,7 @@ class QueryLogging
|
||||
nlog($request->method().' - '.$request->url().": $count queries - ".$time);
|
||||
|
||||
// if($count > 50)
|
||||
// Log::nlog($queries);
|
||||
//nlog($queries);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ use App\Models\Client;
|
||||
use App\Models\GroupSetting;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreClientRequest extends Request
|
||||
{
|
||||
@ -48,7 +49,6 @@ class StoreClientRequest extends Request
|
||||
|
||||
/* Ensure we have a client name, and that all emails are unique*/
|
||||
//$rules['name'] = 'required|min:1';
|
||||
$rules['id_number'] = 'unique:clients,id_number,'.$this->id.',id,company_id,'.$this->company_id;
|
||||
$rules['settings'] = new ValidClientGroupSettingsRule();
|
||||
$rules['contacts.*.email'] = 'bail|nullable|distinct|sometimes|email';
|
||||
$rules['contacts.*.password'] = [
|
||||
@ -66,6 +66,10 @@ class StoreClientRequest extends Request
|
||||
$rules['hosted_clients'] = new CanStoreClientsRule($this->company_id);
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
@ -122,7 +126,7 @@ class StoreClientRequest extends Request
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'unique' => ctrans('validation.unique', ['attribute' => 'email']),
|
||||
// 'unique' => ctrans('validation.unique', ['attribute' => ['email','number']),
|
||||
//'required' => trans('validation.required', ['attribute' => 'email']),
|
||||
'contacts.*.email.required' => ctrans('validation.email', ['attribute' => 'email']),
|
||||
];
|
||||
|
@ -16,6 +16,7 @@ use App\Http\Requests\Request;
|
||||
use App\Http\ValidationRules\ValidClientGroupSettingsRule;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateClientRequest extends Request
|
||||
{
|
||||
@ -52,7 +53,14 @@ class UpdateClientRequest extends Request
|
||||
$rules['country_id'] = 'integer|nullable';
|
||||
$rules['shipping_country_id'] = 'integer|nullable';
|
||||
//$rules['id_number'] = 'unique:clients,id_number,,id,company_id,' . auth()->user()->company()->id;
|
||||
$rules['id_number'] = 'unique:clients,id_number,'.$this->id.',id,company_id,'.$this->company_id;
|
||||
//$rules['id_number'] = 'unique:clients,id_number,'.$this->id.',id,company_id,'.$this->company_id;
|
||||
|
||||
if($this->id_number)
|
||||
$rules['id_number'] = Rule::unique('clients')->where('company_id', auth()->user()->company()->id)->ignore($this->client->id);
|
||||
|
||||
if($this->number)
|
||||
$rules['number'] = Rule::unique('clients')->where('company_id', auth()->user()->company()->id)->ignore($this->client->id);
|
||||
|
||||
$rules['settings'] = new ValidClientGroupSettingsRule();
|
||||
$rules['contacts.*.email'] = 'bail|nullable|distinct|sometimes|email';
|
||||
$rules['contacts.*.password'] = [
|
||||
@ -72,7 +80,6 @@ class UpdateClientRequest extends Request
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'unique' => ctrans('validation.unique', ['attribute' => 'email']),
|
||||
'email' => ctrans('validation.email', ['attribute' => 'email']),
|
||||
'name.required' => ctrans('validation.required', ['attribute' => 'name']),
|
||||
'required' => ctrans('validation.required', ['attribute' => 'email']),
|
||||
|
@ -16,6 +16,7 @@ use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateCreditRequest extends Request
|
||||
{
|
||||
@ -52,9 +53,8 @@ class UpdateCreditRequest extends Request
|
||||
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
|
||||
}
|
||||
|
||||
if ($this->input('number')) {
|
||||
$rules['number'] = 'unique:credits,number,'.$this->id.',id,company_id,'.$this->credit->company_id;
|
||||
}
|
||||
if($this->number)
|
||||
$rules['number'] = Rule::unique('credits')->where('company_id', auth()->user()->company()->id)->ignore($this->credit->id);
|
||||
|
||||
$rules['line_items'] = 'array';
|
||||
|
||||
|
@ -35,10 +35,9 @@ class StoreExpenseRequest extends Request
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
if (isset($this->number)) {
|
||||
if ($this->number)
|
||||
$rules['number'] = Rule::unique('expenses')->where('company_id', auth()->user()->company()->id);
|
||||
}
|
||||
|
||||
|
||||
if(!empty($this->client_id))
|
||||
$rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id;
|
||||
|
||||
|
@ -16,6 +16,7 @@ use App\Http\ValidationRules\Invoice\LockedInvoiceRule;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateInvoiceRequest extends Request
|
||||
{
|
||||
@ -49,10 +50,8 @@ class UpdateInvoiceRequest extends Request
|
||||
|
||||
$rules['id'] = new LockedInvoiceRule($this->invoice);
|
||||
|
||||
// if ($this->input('number') && strlen($this->input('number')) >= 1) {
|
||||
if ($this->input('number')) {
|
||||
$rules['number'] = 'unique:invoices,number,'.$this->id.',id,company_id,'.$this->invoice->company_id;
|
||||
}
|
||||
if($this->number)
|
||||
$rules['number'] = Rule::unique('invoices')->where('company_id', auth()->user()->company()->id)->ignore($this->invoice->id);
|
||||
|
||||
$rules['line_items'] = 'array';
|
||||
|
||||
|
@ -16,6 +16,7 @@ use App\Http\ValidationRules\PaymentAppliedValidAmount;
|
||||
use App\Http\ValidationRules\ValidCreditsPresentRule;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdatePaymentRequest extends Request
|
||||
{
|
||||
@ -35,12 +36,14 @@ class UpdatePaymentRequest extends Request
|
||||
public function rules()
|
||||
{
|
||||
$rules = [
|
||||
'number' => 'nullable|unique:payments,number,'.$this->id.',id,company_id,'.$this->payment->company_id,
|
||||
'invoices' => ['array', new PaymentAppliedValidAmount, new ValidCreditsPresentRule],
|
||||
'invoices.*.invoice_id' => 'distinct',
|
||||
'documents' => 'mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx',
|
||||
];
|
||||
|
||||
if($this->number)
|
||||
$rules['number'] = Rule::unique('payments')->where('company_id', auth()->user()->company()->id)->ignore($this->payment->id);
|
||||
|
||||
if ($this->input('documents') && is_array($this->input('documents'))) {
|
||||
$documents = count($this->input('documents'));
|
||||
|
||||
@ -68,9 +71,9 @@ class UpdatePaymentRequest extends Request
|
||||
unset($input['amount']);
|
||||
}
|
||||
|
||||
if (isset($input['number'])) {
|
||||
unset($input['number']);
|
||||
}
|
||||
// if (isset($input['number'])) {
|
||||
// unset($input['number']);
|
||||
// }
|
||||
|
||||
if (isset($input['invoices']) && is_array($input['invoices']) !== false) {
|
||||
foreach ($input['invoices'] as $key => $value) {
|
||||
|
@ -15,6 +15,7 @@ use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateQuoteRequest extends Request
|
||||
{
|
||||
@ -46,9 +47,8 @@ class UpdateQuoteRequest extends Request
|
||||
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
|
||||
}
|
||||
|
||||
if ($this->input('number')) {
|
||||
$rules['number'] = 'unique:quotes,number,'.$this->id.',id,company_id,'.$this->quote->company_id;
|
||||
}
|
||||
if($this->number)
|
||||
$rules['number'] = Rule::unique('quotes')->where('company_id', auth()->user()->company()->id)->ignore($this->quote->id);
|
||||
|
||||
$rules['line_items'] = 'array';
|
||||
|
||||
|
@ -16,6 +16,7 @@ use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateRecurringInvoiceRequest extends Request
|
||||
{
|
||||
@ -47,9 +48,9 @@ class UpdateRecurringInvoiceRequest extends Request
|
||||
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
|
||||
}
|
||||
|
||||
if ($this->input('number')) {
|
||||
$rules['number'] = 'unique:recurring_invoices,number,'.$this->recurring_invoice->id.',id,company_id,'.$this->recurring_invoice->company_id;
|
||||
}
|
||||
if($this->number)
|
||||
$rules['number'] = Rule::unique('recurring_invoices')->where('company_id', auth()->user()->company()->id)->ignore($this->recurring_invoice->id);
|
||||
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ namespace App\Http\Requests\RecurringQuote;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateRecurringQuoteRequest extends Request
|
||||
{
|
||||
@ -47,6 +48,9 @@ class UpdateRecurringQuoteRequest extends Request
|
||||
|
||||
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
|
||||
if($this->number)
|
||||
$rules['number'] = Rule::unique('recurring_quotes')->where('company_id', auth()->user()->company()->id)->ignore($this->recurring_quote->id);
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace App\Http\Requests\TaxRate;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateTaxRateRequest extends Request
|
||||
{
|
||||
@ -27,10 +28,14 @@ class UpdateTaxRateRequest extends Request
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
// 'name' => 'unique:tax_rates,name,'.$this->tax_rate->name.',id,company_id,'.auth()->user()->companyId(),
|
||||
'name' => 'unique:tax_rates,name,'.$this->id.',id,company_id,'.$this->company_id,
|
||||
'rate' => 'numeric',
|
||||
];
|
||||
$rules = [];
|
||||
|
||||
$rules['rate'] = 'numeric';
|
||||
|
||||
if($this->number)
|
||||
$rules['number'] = Rule::unique('tax_rates')->where('company_id', auth()->user()->company()->id)->ignore($this->tax_rate->id);
|
||||
|
||||
return $rules;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,6 @@ class ReconfirmUserRequest extends Request
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->id == $this->user->id;
|
||||
return auth()->user()->id == $this->user->id || auth()->user()->isAdmin();
|
||||
}
|
||||
}
|
||||
|
19
app/Http/Requests/Vendor/UpdateVendorRequest.php
vendored
19
app/Http/Requests/Vendor/UpdateVendorRequest.php
vendored
@ -14,6 +14,7 @@ namespace App\Http\Requests\Vendor;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateVendorRequest extends Request
|
||||
{
|
||||
@ -35,18 +36,15 @@ class UpdateVendorRequest extends Request
|
||||
/* Ensure we have a client name, and that all emails are unique*/
|
||||
|
||||
$rules['country_id'] = 'integer|nullable';
|
||||
//$rules['id_number'] = 'unique:clients,id_number,,id,company_id,' . auth()->user()->company()->id;
|
||||
$rules['id_number'] = 'unique:clients,id_number,'.$this->id.',id,company_id,'.$this->company_id;
|
||||
|
||||
if($this->number)
|
||||
$rules['number'] = Rule::unique('vendors')->where('company_id', auth()->user()->company()->id)->ignore($this->vendor->id);
|
||||
|
||||
if($this->id_number)
|
||||
$rules['id_number'] = Rule::unique('vendors')->where('company_id', auth()->user()->company()->id)->ignore($this->vendor->id);
|
||||
|
||||
$rules['contacts.*.email'] = 'nullable|distinct';
|
||||
|
||||
$contacts = request('contacts');
|
||||
|
||||
if (is_array($contacts)) {
|
||||
// for ($i = 0; $i < count($contacts); $i++) {
|
||||
// // $rules['contacts.' . $i . '.email'] = 'nullable|email|unique:client_contacts,email,' . isset($contacts[$i]['id'].',company_id,'.$this->company_id);
|
||||
// //$rules['contacts.' . $i . '.email'] = 'nullable|email';
|
||||
// }
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
@ -54,7 +52,6 @@ class UpdateVendorRequest extends Request
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'unique' => ctrans('validation.unique', ['attribute' => 'email']),
|
||||
'email' => ctrans('validation.email', ['attribute' => 'email']),
|
||||
'name.required' => ctrans('validation.required', ['attribute' => 'name']),
|
||||
'required' => ctrans('validation.required', ['attribute' => 'email']),
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
namespace App\Jobs\Entity;
|
||||
|
||||
use App\Models\Account;
|
||||
use App\Models\Credit;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\Design;
|
||||
@ -24,6 +25,7 @@ use App\Models\RecurringInvoiceInvitation;
|
||||
use App\Services\PdfMaker\Design as PdfDesignModel;
|
||||
use App\Services\PdfMaker\Design as PdfMakerDesign;
|
||||
use App\Services\PdfMaker\PdfMaker as PdfMakerService;
|
||||
use App\Utils\HostedPDF\NinjaPdf;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\PhantomJS\Phantom;
|
||||
@ -114,10 +116,13 @@ class CreateEntityPdf implements ShouldQueue
|
||||
$entity_design_id = 'invoice_design_id';
|
||||
}
|
||||
|
||||
$file_path = $path.$this->entity->number.'.pdf';
|
||||
$file_path = $path.$this->entity->numberFormatter().'.pdf';
|
||||
|
||||
$entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey($this->entity->client->getSetting($entity_design_id));
|
||||
|
||||
if(!$this->company->account->hasFeature(Account::FEATURE_DIFFERENT_DESIGNS))
|
||||
$entity_design_id = 2;
|
||||
|
||||
$design = Design::find($entity_design_id);
|
||||
$html = new HtmlEngine($this->invitation);
|
||||
|
||||
@ -156,7 +161,13 @@ class CreateEntityPdf implements ShouldQueue
|
||||
$pdf = null;
|
||||
|
||||
try {
|
||||
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
|
||||
|
||||
if(config('ninja.invoiceninja_hosted_pdf_generation')){
|
||||
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
|
||||
}
|
||||
else {
|
||||
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
nlog(print_r($e->getMessage(), 1));
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ use App\Events\Invoice\InvoiceWasEmailed;
|
||||
use App\Jobs\Entity\EmailEntity;
|
||||
use App\Jobs\Util\WebHookHandler;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Account;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Webhook;
|
||||
use App\Utils\Ninja;
|
||||
@ -207,7 +208,7 @@ class SendReminders implements ShouldQueue
|
||||
$invoice->invitations->each(function ($invitation) use ($template, $invoice) {
|
||||
|
||||
//only send if enable_reminder setting is toggled to yes
|
||||
if ($this->checkSendSetting($invoice, $template)) {
|
||||
if ($this->checkSendSetting($invoice, $template) && $invoice->company->account->hasFeature(Account::FEATURE_EMAIL_TEMPLATES_REMINDERS)) {
|
||||
nlog("firing email");
|
||||
|
||||
EmailEntity::dispatchNow($invitation, $invitation->company, $template);
|
||||
|
@ -80,9 +80,10 @@ class UserEmailChanged implements ShouldQueue
|
||||
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
$nmo->to_user = $this->new_user;
|
||||
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
// $nmo->to_user = $this->new_user;
|
||||
// NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
$this->new_user->service()->invite($this->company);
|
||||
|
||||
}
|
||||
|
||||
|
@ -214,9 +214,14 @@ class Import implements ShouldQueue
|
||||
// if($check_data['status'] == 'errors')
|
||||
// throw new ProcessingMigrationArchiveFailed(implode("\n", $check_data));
|
||||
|
||||
Mail::to($this->user->email, $this->user->name())
|
||||
->send(new MigrationCompleted($this->company, implode("<br>",$check_data)));
|
||||
|
||||
try{
|
||||
Mail::to($this->user->email, $this->user->name())
|
||||
->send(new MigrationCompleted($this->company, implode("<br>",$check_data)));
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
|
||||
/*After a migration first some basic jobs to ensure the system is up to date*/
|
||||
VersionCheck::dispatch();
|
||||
CompanySizeCheck::dispatch();
|
||||
|
@ -88,14 +88,14 @@ class CreditEmailEngine extends BaseEmailEngine
|
||||
->setViewText(ctrans('texts.view_credit'))
|
||||
->setInvitation($this->invitation);
|
||||
|
||||
if ($this->client->getSetting('pdf_email_attachment') !== false) {
|
||||
if ($this->client->getSetting('pdf_email_attachment') !== false && $this->credit->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
|
||||
$this->setAttachments([$this->credit->pdf_file_path()]);
|
||||
|
||||
// $this->setAttachments(['path' => $this->credit->pdf_file_path(), 'name' => basename($this->credit->pdf_file_path())]);
|
||||
}
|
||||
|
||||
//attach third party documents
|
||||
if($this->client->getSetting('document_email_attachment') !== false){
|
||||
if($this->client->getSetting('document_email_attachment') !== false && $this->credit->company->account->hasFeature(Account::FEATURE_DOCUMENTS)){
|
||||
|
||||
// Storage::url
|
||||
foreach($this->credit->documents as $document){
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace App\Mail\Engine;
|
||||
|
||||
use App\DataMapper\EmailTemplateDefaults;
|
||||
use App\Models\Account;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Number;
|
||||
|
||||
@ -97,14 +98,14 @@ class InvoiceEmailEngine extends BaseEmailEngine
|
||||
->setViewText(ctrans('texts.view_invoice'))
|
||||
->setInvitation($this->invitation);
|
||||
|
||||
if ($this->client->getSetting('pdf_email_attachment') !== false) {
|
||||
if ($this->client->getSetting('pdf_email_attachment') !== false && $this->invoice->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
|
||||
$this->setAttachments([$this->invoice->pdf_file_path()]);
|
||||
// $this->setAttachments(['path' => $this->invoice->pdf_file_path(), 'name' => basename($this->invoice->pdf_file_path())]);
|
||||
|
||||
}
|
||||
|
||||
//attach third party documents
|
||||
if($this->client->getSetting('document_email_attachment') !== false){
|
||||
if($this->client->getSetting('document_email_attachment') !== false && $this->invoice->company->account->hasFeature(Account::FEATURE_DOCUMENTS)){
|
||||
|
||||
// Storage::url
|
||||
foreach($this->invoice->documents as $document){
|
||||
|
@ -89,14 +89,14 @@ class QuoteEmailEngine extends BaseEmailEngine
|
||||
->setInvitation($this->invitation);
|
||||
|
||||
|
||||
if ($this->client->getSetting('pdf_email_attachment') !== false) {
|
||||
if ($this->client->getSetting('pdf_email_attachment') !== false && $this->quote->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
|
||||
$this->setAttachments([$this->quote->pdf_file_path()]);
|
||||
//$this->setAttachments(['path' => $this->quote->pdf_file_path(), 'name' => basename($this->quote->pdf_file_path())]);
|
||||
|
||||
}
|
||||
|
||||
//attach third party documents
|
||||
if($this->client->getSetting('document_email_attachment') !== false){
|
||||
if($this->client->getSetting('document_email_attachment') !== false && $this->quote->company->account->hasFeature(Account::FEATURE_DOCUMENTS)){
|
||||
|
||||
// Storage::url
|
||||
foreach($this->quote->documents as $document){
|
||||
|
@ -186,6 +186,15 @@ class BaseModel extends Model
|
||||
*/
|
||||
public function getFileName($extension = 'pdf')
|
||||
{
|
||||
return $this->number.'.'.$extension;
|
||||
return $this->numberFormatter().'.'.$extension;
|
||||
}
|
||||
|
||||
public function numberFormatter()
|
||||
{
|
||||
$formatted_number = mb_ereg_replace("([^\w\s\d\-_~,;\[\]\(\).])", '', $this->number);
|
||||
// Remove any runs of periods (thanks falstro!)
|
||||
$formatted_number = mb_ereg_replace("([\.]{2,})", '', $formatted_number);
|
||||
|
||||
return $formatted_number;
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Services\BillingSubscription\BillingSubscriptionService;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
@ -53,6 +54,11 @@ class BillingSubscription extends BaseModel
|
||||
'deleted_at' => 'timestamp',
|
||||
];
|
||||
|
||||
public function service()
|
||||
{
|
||||
return new BillingSubscriptionService($this);
|
||||
}
|
||||
|
||||
public function company(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
@ -67,5 +73,4 @@ class BillingSubscription extends BaseModel
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -304,6 +304,10 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
return $this->company->settings->{$setting};
|
||||
}
|
||||
|
||||
elseif( property_exists(CompanySettings::defaults(), $setting) ) {
|
||||
return CompanySettings::defaults()->{$setting};
|
||||
}
|
||||
|
||||
return '';
|
||||
|
||||
// throw new \Exception("Settings corrupted", 1);
|
||||
|
@ -21,6 +21,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Laracasts\Presenter\PresentableTrait;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class Company extends BaseModel
|
||||
{
|
||||
@ -286,7 +287,7 @@ class Company extends BaseModel
|
||||
*/
|
||||
public function country()
|
||||
{
|
||||
//return $this->belongsTo(Country::class);
|
||||
// return $this->belongsTo(Country::class);
|
||||
return Country::find($this->settings->country_id);
|
||||
}
|
||||
|
||||
@ -342,12 +343,13 @@ class Company extends BaseModel
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo
|
||||
*/
|
||||
public function currency()
|
||||
{
|
||||
return $this->belongsTo(Currency::class);
|
||||
$currencies = Cache::get('currencies');
|
||||
|
||||
return $currencies->filter(function ($item) {
|
||||
return $item->id == $this->settings->currency_id;
|
||||
})->first();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -248,9 +248,9 @@ class Credit extends BaseModel
|
||||
|
||||
public function pdf_file_path($invitation = null)
|
||||
{
|
||||
$storage_path = Storage::url($this->client->credit_filepath().$this->number.'.pdf');
|
||||
$storage_path = Storage::url($this->client->credit_filepath().$this->numberFormatter().'.pdf');
|
||||
|
||||
if (Storage::exists($this->client->credit_filepath().$this->number.'.pdf')) {
|
||||
if (Storage::exists($this->client->credit_filepath().$this->numberFormatter().'.pdf')) {
|
||||
return $storage_path;
|
||||
}
|
||||
|
||||
|
@ -126,9 +126,9 @@ class CreditInvitation extends BaseModel
|
||||
|
||||
public function pdf_file_path()
|
||||
{
|
||||
$storage_path = Storage::url($this->credit->client->quote_filepath().$this->credit->number.'.pdf');
|
||||
$storage_path = Storage::url($this->credit->client->quote_filepath().$this->credit->numberFormatter().'.pdf');
|
||||
|
||||
if (! Storage::exists($this->credit->client->credit_filepath().$this->credit->number.'.pdf')) {
|
||||
if (! Storage::exists($this->credit->client->credit_filepath().$this->credit->numberFormatter().'.pdf')) {
|
||||
event(new CreditWasUpdated($this, $this->company, Ninja::eventVars()));
|
||||
CreateEntityPdf::dispatchNow($this);
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ class Gateway extends StaticModel
|
||||
* Returns an array of methods and the gatewaytypes possible
|
||||
*
|
||||
* @return array
|
||||
*///todo remove methods replace with gatewaytype:: and then nest refund / token billing
|
||||
*/
|
||||
public function getMethods()
|
||||
{
|
||||
switch ($this->id) {
|
||||
|
@ -388,9 +388,9 @@ class Invoice extends BaseModel
|
||||
$invitation = $this->invitations->first();
|
||||
}
|
||||
|
||||
$storage_path = Storage::$type($this->client->invoice_filepath().$this->number.'.pdf');
|
||||
$storage_path = Storage::$type($this->client->invoice_filepath().$this->numberFormatter().'.pdf');
|
||||
|
||||
if (! Storage::exists($this->client->invoice_filepath().$this->number.'.pdf')) {
|
||||
if (! Storage::exists($this->client->invoice_filepath().$this->numberFormatter().'.pdf')) {
|
||||
event(new InvoiceWasUpdated($this, $this->company, Ninja::eventVars()));
|
||||
CreateEntityPdf::dispatchNow($invitation);
|
||||
}
|
||||
|
@ -140,9 +140,9 @@ class InvoiceInvitation extends BaseModel
|
||||
|
||||
public function pdf_file_path()
|
||||
{
|
||||
$storage_path = Storage::url($this->invoice->client->invoice_filepath().$this->invoice->number.'.pdf');
|
||||
$storage_path = Storage::url($this->invoice->client->invoice_filepath().$this->invoice->numberFormatter().'.pdf');
|
||||
|
||||
if (! Storage::exists($this->invoice->client->invoice_filepath().$this->invoice->number.'.pdf')) {
|
||||
if (! Storage::exists($this->invoice->client->invoice_filepath().$this->invoice->numberFormatter().'.pdf')) {
|
||||
event(new InvoiceWasUpdated($this->invoice, $this->company, Ninja::eventVars()));
|
||||
CreateEntityPdf::dispatchNow($this);
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ class Project extends BaseModel
|
||||
'custom_value4',
|
||||
'assigned_user_id',
|
||||
'color',
|
||||
'number',
|
||||
];
|
||||
|
||||
public function getEntityType()
|
||||
|
@ -208,11 +208,11 @@ class Quote extends BaseModel
|
||||
$invitation = $this->invitations->first();
|
||||
}
|
||||
|
||||
$storage_path = Storage::$type($this->client->quote_filepath().$this->number.'.pdf');
|
||||
$storage_path = Storage::$type($this->client->quote_filepath().$this->numberFormatter().'.pdf');
|
||||
|
||||
nlog($storage_path);
|
||||
|
||||
if (! Storage::exists($this->client->quote_filepath().$this->number.'.pdf')) {
|
||||
if (! Storage::exists($this->client->quote_filepath().$this->numberFormatter().'.pdf')) {
|
||||
event(new QuoteWasUpdated($this, $this->company, Ninja::eventVars()));
|
||||
CreateEntityPdf::dispatchNow($invitation);
|
||||
}
|
||||
|
@ -130,9 +130,9 @@ class QuoteInvitation extends BaseModel
|
||||
|
||||
public function pdf_file_path()
|
||||
{
|
||||
$storage_path = Storage::url($this->quote->client->quote_filepath().$this->quote->number.'.pdf');
|
||||
$storage_path = Storage::url($this->quote->client->quote_filepath().$this->quote->numberFormatter().'.pdf');
|
||||
|
||||
if (! Storage::exists($this->quote->client->quote_filepath().$this->quote->number.'.pdf')) {
|
||||
if (! Storage::exists($this->quote->client->quote_filepath().$this->quote->numberFormatter().'.pdf')) {
|
||||
event(new QuoteWasUpdated($this->quote, $this->company, Ninja::eventVars()));
|
||||
CreateEntityPdf::dispatchNow($this);
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ class InvoiceObserver
|
||||
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_INVOICE, $invoice, $invoice->company);
|
||||
}
|
||||
|
||||
// UnlinkFile::dispatchNow(config('filesystems.default'), $invoice->client->invoice_filepath() . $invoice->number.'.pdf');
|
||||
// UnlinkFile::dispatchNow(config('filesystems.default'), $invoice->client->invoice_filepath() . $invoice->numberFormatter().'.pdf');
|
||||
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\SystemLog;
|
||||
use App\Services\BillingSubscription\BillingSubscriptionService;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\SystemLogTrait;
|
||||
@ -207,7 +208,7 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
public function createPayment($data, $status = Payment::STATUS_COMPLETED): Payment
|
||||
{
|
||||
$this->confirmGatewayFee();
|
||||
|
||||
|
||||
$payment = PaymentFactory::create($this->client->company->id, $this->client->user->id);
|
||||
$payment->client_id = $this->client->id;
|
||||
$payment->company_gateway_id = $this->company_gateway->id;
|
||||
@ -240,6 +241,8 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
|
||||
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));
|
||||
|
||||
//(new BillingSubscriptionService)->completePurchase($this->payment_hash);
|
||||
|
||||
return $payment->service()->applyNumber()->save();
|
||||
}
|
||||
|
||||
@ -345,8 +348,8 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
}
|
||||
else if ($e instanceof Exception) {
|
||||
$error = $e->getMessage();
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
$error = $e->getMessage();
|
||||
|
||||
PaymentFailureMailer::dispatch(
|
||||
|
@ -31,7 +31,7 @@ class DriverTemplate extends BaseDriver
|
||||
GatewayType::CREDIT_CARD => CreditCard::class, //maps GatewayType => Implementation class
|
||||
];
|
||||
|
||||
const SYSTEM_LOG_TYPE = SystemLog::TYPE_STRIPE;
|
||||
const SYSTEM_LOG_TYPE = SystemLog::TYPE_STRIPE; //define a constant for your gateway ie TYPE_YOUR_CUSTOM_GATEWAY - set the const in the SystemLog model
|
||||
|
||||
public function setPaymentMethod($payment_method_id)
|
||||
{
|
||||
|
@ -136,7 +136,7 @@ class PayPalExpressPaymentDriver extends BaseDriver
|
||||
$payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $response, 'data' => $data],
|
||||
['response' => (array)$response->getData(), 'data' => $data],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_PAYPAL,
|
||||
|
@ -23,7 +23,7 @@ use Illuminate\Support\Str;
|
||||
class ClientContactRepository extends BaseRepository
|
||||
{
|
||||
public $is_primary;
|
||||
|
||||
|
||||
public function save(array $data, Client $client) : void
|
||||
{
|
||||
if (isset($data['contacts'])) {
|
||||
@ -37,6 +37,7 @@ class ClientContactRepository extends BaseRepository
|
||||
});
|
||||
|
||||
$this->is_primary = true;
|
||||
|
||||
/* Set first record to primary - always */
|
||||
$contacts = $contacts->sortByDesc('is_primary')->map(function ($contact) {
|
||||
$contact['is_primary'] = $this->is_primary;
|
||||
|
174
app/Services/BillingSubscription/BillingSubscriptionService.php
Normal file
174
app/Services/BillingSubscription/BillingSubscriptionService.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?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://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Services\BillingSubscription;
|
||||
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Models\BillingSubscription;
|
||||
use App\Models\ClientSubscription;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\Product;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
|
||||
class BillingSubscriptionService
|
||||
{
|
||||
/** @var BillingSubscription */
|
||||
private $billing_subscription;
|
||||
|
||||
public function __construct(BillingSubscription $billing_subscription)
|
||||
{
|
||||
$this->billing_subscription = $billing_subscription;
|
||||
}
|
||||
|
||||
public function completePurchase(PaymentHash $payment_hash)
|
||||
{
|
||||
|
||||
if (!property_exists($payment_hash, 'billing_context')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point we have some state carried from the billing page
|
||||
// to this, available as $payment_hash->data->billing_context. Make something awesome ⭐
|
||||
|
||||
// create client subscription record
|
||||
//
|
||||
// create recurring invoice if is_recurring
|
||||
//
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function startTrial(array $data)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function createInvoice($data): ?\App\Models\Invoice
|
||||
{
|
||||
$invoice_repo = new InvoiceRepository();
|
||||
|
||||
$data['line_items'] = $this->createLineItems($data);
|
||||
|
||||
/*
|
||||
If trial_enabled -> return early
|
||||
|
||||
-- what we need to know that we don't already
|
||||
-- Has a promo code been entered, and does it match
|
||||
-- Is this a recurring subscription
|
||||
--
|
||||
|
||||
1. Is this a recurring product?
|
||||
2. What is the quantity? ie is this a multi seat product ( does this mean we need this value stored in the client sub?)
|
||||
*/
|
||||
|
||||
return $invoice_repo->save($data, InvoiceFactory::create($this->billing_subscription->company_id, $this->billing_subscription->user_id));
|
||||
|
||||
}
|
||||
|
||||
private function createLineItems($data): array
|
||||
{
|
||||
$line_items = [];
|
||||
|
||||
$product = $this->billing_subscription->product;
|
||||
|
||||
$item = new InvoiceItem;
|
||||
$item->quantity = $data['quantity'];
|
||||
$item->product_key = $product->product_key;
|
||||
$item->notes = $product->notes;
|
||||
$item->cost = $product->price;
|
||||
$item->tax_rate1 = $product->tax_rate1 ?: 0;
|
||||
$item->tax_name1 = $product->tax_name1 ?: '';
|
||||
$item->tax_rate2 = $product->tax_rate2 ?: 0;
|
||||
$item->tax_name2 = $product->tax_name2 ?: '';
|
||||
$item->tax_rate3 = $product->tax_rate3 ?: 0;
|
||||
$item->tax_name3 = $product->tax_name3 ?: '';
|
||||
$item->custom_value1 = $product->custom_value1 ?: '';
|
||||
$item->custom_value2 = $product->custom_value2 ?: '';
|
||||
$item->custom_value3 = $product->custom_value3 ?: '';
|
||||
$item->custom_value4 = $product->custom_value4 ?: '';
|
||||
|
||||
//$item->type_id need to switch whether the subscription is a service or product
|
||||
|
||||
$line_items[] = $item;
|
||||
|
||||
|
||||
//do we have a promocode? enter this as a line item.
|
||||
if(strlen($data['coupon']) >=1 && ($data['coupon'] == $this->billing_subscription->promo_code) && $this->billing_subscription->promo_discount > 0)
|
||||
$line_items[] = $this->createPromoLine($data);
|
||||
|
||||
return $line_items;
|
||||
}
|
||||
|
||||
private function createPromoLine($data)
|
||||
{
|
||||
|
||||
$product = $this->billing_subscription->product;
|
||||
|
||||
$discounted_amount = 0;
|
||||
$discount = 0;
|
||||
$amount = $data['quantity'] * $product->cost;
|
||||
|
||||
if ($this->billing_subscription->is_amount_discount == true) {
|
||||
$discount = $this->billing_subscription->promo_discount;
|
||||
}
|
||||
else {
|
||||
$discount = round($amount * ($this->billing_subscription->promo_discount / 100), 2);
|
||||
}
|
||||
|
||||
$discounted_amount = $amount - $discount;
|
||||
|
||||
$item = new InvoiceItem;
|
||||
$item->quantity = 1;
|
||||
$item->product_key = ctrans('texts.promo_code');
|
||||
$item->notes = ctrans('texts.promo_code');
|
||||
$item->cost = $discounted_amount;
|
||||
$item->tax_rate1 = $product->tax_rate1 ?: 0;
|
||||
$item->tax_name1 = $product->tax_name1 ?: '';
|
||||
$item->tax_rate2 = $product->tax_rate2 ?: 0;
|
||||
$item->tax_name2 = $product->tax_name2 ?: '';
|
||||
$item->tax_rate3 = $product->tax_rate3 ?: 0;
|
||||
$item->tax_name3 = $product->tax_name3 ?: '';
|
||||
|
||||
return $item;
|
||||
|
||||
}
|
||||
|
||||
private function convertInvoiceToRecurring()
|
||||
{
|
||||
//The first invoice is a plain invoice - the second is fired on the recurring schedule.
|
||||
}
|
||||
|
||||
public function createClientSubscription($payment_hash, $recurring_invoice_id = null)
|
||||
{
|
||||
//create the client sub record
|
||||
|
||||
//?trial enabled?
|
||||
$cs = new ClientSubscription();
|
||||
$cs->subscription_id = $this->billing_subscription->id;
|
||||
$cs->company_id = $this->billing_subscription->company_id;
|
||||
|
||||
// client_id
|
||||
$cs->save();
|
||||
}
|
||||
|
||||
public function triggerWebhook($payment_hash)
|
||||
{
|
||||
//hit the webhook to after a successful onboarding
|
||||
}
|
||||
|
||||
public function fireNotifications()
|
||||
{
|
||||
//scan for any notification we are required to send
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -140,7 +140,7 @@ class CreditService
|
||||
|
||||
public function deletePdf()
|
||||
{
|
||||
UnlinkFile::dispatchNow(config('filesystems.default'), $this->credit->client->credit_filepath() . $this->credit->number.'.pdf');
|
||||
UnlinkFile::dispatchNow(config('filesystems.default'), $this->credit->client->credit_filepath() . $this->credit->numberFormatter().'.pdf');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class GetCreditPdf extends AbstractService
|
||||
|
||||
$path = $this->credit->client->credit_filepath();
|
||||
|
||||
$file_path = $path.$this->credit->number.'.pdf';
|
||||
$file_path = $path.$this->credit->numberFormatter().'.pdf';
|
||||
|
||||
$disk = config('filesystems.default');
|
||||
|
||||
|
@ -37,7 +37,7 @@ class GetInvoicePdf extends AbstractService
|
||||
|
||||
$path = $this->invoice->client->invoice_filepath();
|
||||
|
||||
$file_path = $path.$this->invoice->number.'.pdf';
|
||||
$file_path = $path.$this->invoice->numberFormatter().'.pdf';
|
||||
|
||||
$disk = config('filesystems.default');
|
||||
|
||||
|
@ -274,8 +274,8 @@ class InvoiceService
|
||||
|
||||
public function deletePdf()
|
||||
{
|
||||
//UnlinkFile::dispatchNow(config('filesystems.default'), $this->invoice->client->invoice_filepath() . $this->invoice->number.'.pdf');
|
||||
Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath() . $this->invoice->number.'.pdf');
|
||||
//UnlinkFile::dispatchNow(config('filesystems.default'), $this->invoice->client->invoice_filepath() . $this->invoice->numberFormatter().'.pdf');
|
||||
Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath() . $this->invoice->numberFormatter().'.pdf');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -302,7 +302,7 @@ class Design extends BaseDesign
|
||||
public function buildTableHeader(string $type): array
|
||||
{
|
||||
$this->processTaxColumns($type);
|
||||
$this->processCustomColumns($type);
|
||||
// $this->processCustomColumns($type);
|
||||
|
||||
$elements = [];
|
||||
|
||||
|
@ -213,6 +213,7 @@ trait PdfMakerUtilities
|
||||
$css = <<<'EOT'
|
||||
table.page-container {
|
||||
page-break-after: always;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
thead.page-header {
|
||||
|
@ -36,7 +36,7 @@ class GetQuotePdf extends AbstractService
|
||||
|
||||
$path = $this->quote->client->quote_filepath();
|
||||
|
||||
$file_path = $path.$this->quote->number.'.pdf';
|
||||
$file_path = $path.$this->quote->numberFormatter().'.pdf';
|
||||
|
||||
$disk = config('filesystems.default');
|
||||
|
||||
|
@ -178,7 +178,7 @@ class QuoteService
|
||||
|
||||
public function deletePdf()
|
||||
{
|
||||
UnlinkFile::dispatchNow(config('filesystems.default'), $this->quote->client->quote_filepath() . $this->quote->number.'.pdf');
|
||||
UnlinkFile::dispatchNow(config('filesystems.default'), $this->quote->client->quote_filepath() . $this->quote->numberFormatter().'.pdf');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ class RecurringService
|
||||
|
||||
public function deletePdf()
|
||||
{
|
||||
UnlinkFile::dispatchNow(config('filesystems.default'), $this->recurring_entity->client->recurring_invoice_filepath() . $this->recurring_entity->number.'.pdf');
|
||||
UnlinkFile::dispatchNow(config('filesystems.default'), $this->recurring_entity->client->recurring_invoice_filepath() . $this->recurring_entity->numberFormatter().'.pdf');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -78,6 +78,7 @@ class AccountTransformer extends EntityTransformer
|
||||
'is_docker' => (bool) config('ninja.is_docker'),
|
||||
'is_scheduler_running' => (bool) $account->is_scheduler_running,
|
||||
'default_company_id' => (string) $this->encodePrimaryKey($account->default_company_id),
|
||||
'disable_auto_update' => (bool) config('ninja.disable_auto_update'),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -57,6 +57,7 @@ class BillingSubscriptionTransformer extends EntityTransformer
|
||||
'plan_map' => (string)$billing_subscription->plan_map,
|
||||
'refund_period' => (int)$billing_subscription->refund_period,
|
||||
'webhook_configuration' => (string)$billing_subscription->webhook_configuration,
|
||||
'purchase_page' => (string)route('client.subscription.purchase', $billing_subscription->hashed_id),
|
||||
'is_deleted' => (bool)$billing_subscription->is_deleted,
|
||||
'created_at' => (int)$billing_subscription->created_at,
|
||||
'updated_at' => (int)$billing_subscription->updated_at,
|
||||
|
@ -16,6 +16,7 @@ use App\Models\Client;
|
||||
use App\Models\Document;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\Payment;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class InvoiceTransformer extends EntityTransformer
|
||||
@ -30,7 +31,7 @@ class InvoiceTransformer extends EntityTransformer
|
||||
protected $availableIncludes = [
|
||||
// 'invitations',
|
||||
'history',
|
||||
// 'payments',
|
||||
'payments',
|
||||
'client',
|
||||
// 'documents',
|
||||
];
|
||||
@ -56,15 +57,15 @@ class InvoiceTransformer extends EntityTransformer
|
||||
return $this->includeItem($invoice->client, $transformer, Client::class);
|
||||
}
|
||||
|
||||
|
||||
public function includePayments(Invoice $invoice)
|
||||
{
|
||||
$transformer = new PaymentTransformer( $this->serializer);
|
||||
|
||||
return $this->includeCollection($invoice->payments, $transformer, Payment::class);
|
||||
}
|
||||
|
||||
/*
|
||||
public function includePayments(Invoice $invoice)
|
||||
{
|
||||
$transformer = new PaymentTransformer($this->account, $this->serializer, $invoice);
|
||||
|
||||
return $this->includeCollection($invoice->payments, $transformer, ENTITY_PAYMENT);
|
||||
}
|
||||
|
||||
|
||||
public function includeExpenses(Invoice $invoice)
|
||||
{
|
||||
$transformer = new ExpenseTransformer($this->account, $this->serializer);
|
||||
|
37
app/Utils/HostedPDF/NinjaPdf.php
Normal file
37
app/Utils/HostedPDF/NinjaPdf.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?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://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Utils\HostedPDF;
|
||||
|
||||
use GuzzleHttp\RequestOptions;
|
||||
|
||||
class NinjaPdf
|
||||
{
|
||||
|
||||
private $url = 'https://pdf.invoicing.co/api/';
|
||||
|
||||
public function build($html)
|
||||
{
|
||||
|
||||
$client = new \GuzzleHttp\Client(['headers' =>
|
||||
[
|
||||
'X-Ninja-Token' => 'test_token_for_now',
|
||||
]
|
||||
]);
|
||||
|
||||
$response = $client->post($this->url,[
|
||||
RequestOptions::JSON => ['html' => $html]
|
||||
]);
|
||||
|
||||
return $response->getBody();
|
||||
}
|
||||
|
||||
}
|
@ -285,6 +285,8 @@ class HtmlEngine
|
||||
$data['$company.website'] = ['value' => $this->settings->website ?: ' ', 'label' => ctrans('texts.website')];
|
||||
$data['$company.address'] = ['value' => $this->company->present()->address($this->settings) ?: ' ', 'label' => ctrans('texts.address')];
|
||||
|
||||
$data['$signature'] = ['value' => $this->settings->email_signature ?: ' ', 'label' => ''];
|
||||
|
||||
$data['$spc_qr_code'] = ['value' => $this->company->present()->getSpcQrCode($this->client->currency()->code, $this->entity->number, $this->entity->balance), 'label' => ''];
|
||||
|
||||
$logo = $this->company->present()->logo($this->settings);
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\Currency;
|
||||
|
||||
/**
|
||||
@ -83,17 +84,17 @@ class Number
|
||||
|
||||
return floatval($value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Formats a given value based on the clients currency AND country.
|
||||
*
|
||||
* @param floatval $value The number to be formatted
|
||||
* @param $client
|
||||
* @param $entity
|
||||
* @return string The formatted value
|
||||
*/
|
||||
public static function formatMoney($value, $client) :string
|
||||
public static function formatMoney($value, $entity) :string
|
||||
{
|
||||
$currency = $client->currency();
|
||||
$currency = $entity->currency();
|
||||
|
||||
$thousand = $currency->thousand_separator;
|
||||
$decimal = $currency->decimal_separator;
|
||||
@ -101,29 +102,38 @@ class Number
|
||||
$code = $currency->code;
|
||||
$swapSymbol = $currency->swap_currency_symbol;
|
||||
|
||||
// App\Models\Client::country() returns instance of BelongsTo.
|
||||
// App\Models\Company::country() returns record for the country, that's why we check for the instance.
|
||||
|
||||
if ($entity instanceof Company) {
|
||||
$country = $entity->country();
|
||||
} else {
|
||||
$country = $entity->country;
|
||||
}
|
||||
|
||||
/* Country settings override client settings */
|
||||
if (isset($client->country->thousand_separator) && strlen($client->country->thousand_separator) >= 1) {
|
||||
$thousand = $client->country->thousand_separator;
|
||||
if (isset($country->thousand_separator) && strlen($country->thousand_separator) >= 1) {
|
||||
$thousand = $country->thousand_separator;
|
||||
}
|
||||
|
||||
if (isset($client->country->decimal_separator) && strlen($client->country->decimal_separator) >= 1) {
|
||||
$decimal = $client->country->decimal_separator;
|
||||
if (isset($country->decimal_separator) && strlen($country->decimal_separator) >= 1) {
|
||||
$decimal = $country->decimal_separator;
|
||||
}
|
||||
|
||||
if (isset($client->country->swap_currency_symbol) && strlen($client->country->swap_currency_symbol) >= 1) {
|
||||
$swapSymbol = $client->country->swap_currency_symbol;
|
||||
if (isset($country->swap_currency_symbol) && strlen($country->swap_currency_symbol) >= 1) {
|
||||
$swapSymbol = $country->swap_currency_symbol;
|
||||
}
|
||||
|
||||
$value = number_format($value, $precision, $decimal, $thousand);
|
||||
$symbol = $currency->symbol;
|
||||
|
||||
if ($client->getSetting('show_currency_code') === true && $currency->code == 'CHF') {
|
||||
if ($entity->getSetting('show_currency_code') === true && $currency->code == 'CHF') {
|
||||
return "{$code} {$value}";
|
||||
} elseif ($client->getSetting('show_currency_code') === true) {
|
||||
} elseif ($entity->getSetting('show_currency_code') === true) {
|
||||
return "{$value} {$code}";
|
||||
} elseif ($swapSymbol) {
|
||||
return "{$value} ".trim($symbol);
|
||||
} elseif ($client->getSetting('show_currency_code') === false) {
|
||||
} elseif ($entity->getSetting('show_currency_code') === false) {
|
||||
return "{$symbol}{$value}";
|
||||
} else {
|
||||
return self::formatValue($value, $currency);
|
||||
|
@ -76,7 +76,7 @@ class Phantom
|
||||
$path = $entity_obj->client->recurring_invoice_filepath();
|
||||
}
|
||||
|
||||
$file_path = $path.$entity_obj->number.'.pdf';
|
||||
$file_path = $path.$entity_obj->numberFormatter().'.pdf';
|
||||
|
||||
$url = config('ninja.app_url').'/phantom/'.$entity.'/'.$invitation->key.'?phantomjs_secret='.config('ninja.phantomjs_secret');
|
||||
info($url);
|
||||
@ -91,8 +91,8 @@ class Phantom
|
||||
|
||||
$instance = Storage::disk(config('filesystems.default'))->put($file_path, $pdf);
|
||||
|
||||
nlog($instance);
|
||||
nlog($file_path);
|
||||
// nlog($instance);
|
||||
// nlog($file_path);
|
||||
return $file_path;
|
||||
}
|
||||
|
||||
|
@ -78,9 +78,15 @@ class SystemHealth
|
||||
'phantom_enabled' => (bool)config('ninja.phantomjs_pdf_generation'),
|
||||
'exec' => (bool)self::checkExecWorks(),
|
||||
'open_basedir' => (bool)self::checkOpenBaseDir(),
|
||||
'mail_mailer' => (string)self::checkMailMailer(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function checkMailMailer()
|
||||
{
|
||||
return config('mail.default');
|
||||
}
|
||||
|
||||
public static function checkOpenBaseDir()
|
||||
{
|
||||
if (strlen(ini_get('open_basedir') == 0)) {
|
||||
|
@ -134,6 +134,9 @@ trait GeneratesCounter
|
||||
return 'payment_number_counter';
|
||||
break;
|
||||
case Credit::class:
|
||||
if ($this->hasSharedCounter($client))
|
||||
return 'invoice_number_counter';
|
||||
|
||||
return 'credit_number_counter';
|
||||
break;
|
||||
case Project::class:
|
||||
@ -313,7 +316,7 @@ trait GeneratesCounter
|
||||
*/
|
||||
public function hasSharedCounter(Client $client) : bool
|
||||
{
|
||||
return (bool) $client->getSetting('shared_invoice_quote_counter');
|
||||
return (bool) $client->getSetting('shared_invoice_quote_counter') || (bool) $client->getSetting('shared_invoice_credit_counter');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,7 +34,7 @@ trait UserNotifies
|
||||
array_push($required_permissions, 'all_user_notifications');
|
||||
}
|
||||
|
||||
if (count(array_intersect($required_permissions, $notifications->email)) >= 1 || count(array_intersect($required_permissions, 'all_user_notifications')) >= 1 || count(array_intersect($required_permissions, 'all_notifications')) >= 1) {
|
||||
if (count(array_intersect($required_permissions, $notifications->email)) >= 1 || count(array_intersect($required_permissions, ['all_user_notifications'])) >= 1 || count(array_intersect($required_permissions, ['all_notifications'])) >= 1) {
|
||||
array_push($notifiable_methods, 'mail');
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ trait SavesDocuments
|
||||
{
|
||||
public function saveDocuments($document_array, $entity, $is_public = true)
|
||||
{
|
||||
|
||||
if ($entity instanceof Company) {
|
||||
$account = $entity->account;
|
||||
$company = $entity;
|
||||
|
@ -38,7 +38,7 @@
|
||||
"coconutcraig/laravel-postmark": "^2.10",
|
||||
"composer/composer": "^2",
|
||||
"czproject/git-php": "^3.17",
|
||||
"dacastro4/laravel-gmail": "dev-master",
|
||||
"dacastro4/laravel-gmail": "^5.1",
|
||||
"doctrine/dbal": "^2.10",
|
||||
"fideloper/proxy": "^4.2",
|
||||
"fzaninotto/faker": "^1.4",
|
||||
@ -65,12 +65,13 @@
|
||||
"predis/predis": "^1.1",
|
||||
"sentry/sentry-laravel": "^2",
|
||||
"stripe/stripe-php": "^7.50",
|
||||
"symfony/http-client": "^5.2",
|
||||
"turbo124/beacon": "^1.0",
|
||||
"webpatser/laravel-countries": "dev-master#75992ad",
|
||||
"wildbit/swiftmailer-postmark": "^3.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"php": "^7.4",
|
||||
"php": "^7.3|^7.4",
|
||||
"anahkiasen/former": "^4.2",
|
||||
"barryvdh/laravel-debugbar": "^3.4",
|
||||
"brianium/paratest": "^6.1",
|
||||
|
158
composer.lock
generated
158
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "d9ca08ed1ffaa0ed07c40e014a96d52a",
|
||||
"content-hash": "113acad46f6d9eea9f9f5bd4501428d1",
|
||||
"packages": [
|
||||
{
|
||||
"name": "authorizenet/authorizenet",
|
||||
@ -55,16 +55,16 @@
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.173.25",
|
||||
"version": "3.173.28",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "69e4653acf8cd855e9010ec4e0e0a7d074c77ee1"
|
||||
"reference": "593baa5930896bb443c437032daf4016e1e3878d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/69e4653acf8cd855e9010ec4e0e0a7d074c77ee1",
|
||||
"reference": "69e4653acf8cd855e9010ec4e0e0a7d074c77ee1",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/593baa5930896bb443c437032daf4016e1e3878d",
|
||||
"reference": "593baa5930896bb443c437032daf4016e1e3878d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -139,9 +139,9 @@
|
||||
"support": {
|
||||
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.173.25"
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.173.28"
|
||||
},
|
||||
"time": "2021-03-09T19:14:56+00:00"
|
||||
"time": "2021-03-12T19:29:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
@ -1011,16 +1011,16 @@
|
||||
},
|
||||
{
|
||||
"name": "dacastro4/laravel-gmail",
|
||||
"version": "dev-master",
|
||||
"version": "v5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dacastro4/laravel-gmail.git",
|
||||
"reference": "a7137e7e7aa0672ec963e4d24da6c5f22a0208c0"
|
||||
"reference": "6d4cabe04f8cdd02b25ef73a1a489099b5e790bd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dacastro4/laravel-gmail/zipball/a7137e7e7aa0672ec963e4d24da6c5f22a0208c0",
|
||||
"reference": "a7137e7e7aa0672ec963e4d24da6c5f22a0208c0",
|
||||
"url": "https://api.github.com/repos/dacastro4/laravel-gmail/zipball/6d4cabe04f8cdd02b25ef73a1a489099b5e790bd",
|
||||
"reference": "6d4cabe04f8cdd02b25ef73a1a489099b5e790bd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1031,16 +1031,15 @@
|
||||
"illuminate/routing": "~5.8|^6.0|^7.0|^8.0",
|
||||
"illuminate/session": "~5.8|^6.0|^7.0|^8.0",
|
||||
"illuminate/support": "~5.8|^6.0|^7.0|^8.0",
|
||||
"php": "^7.3|^8.0",
|
||||
"php": "^7.2",
|
||||
"swiftmailer/swiftmailer": "~5.8|^6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.0",
|
||||
"orchestra/testbench": "^4.0|^5.0|^6.0",
|
||||
"phpunit/phpunit": "^8.5|^9.0",
|
||||
"orchestra/testbench": "^4.0",
|
||||
"phpunit/phpunit": "^8.5",
|
||||
"squizlabs/php_codesniffer": "~3.4"
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
@ -1076,9 +1075,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/dacastro4/laravel-gmail/issues",
|
||||
"source": "https://github.com/dacastro4/laravel-gmail/tree/master"
|
||||
"source": "https://github.com/dacastro4/laravel-gmail/tree/v5.1"
|
||||
},
|
||||
"time": "2021-03-02T17:18:12+00:00"
|
||||
"time": "2020-12-13T19:17:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dasprid/enum",
|
||||
@ -5447,16 +5446,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpseclib/phpseclib",
|
||||
"version": "3.0.5",
|
||||
"version": "3.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||
"reference": "7c751ea006577e4c2e83326d90c8b1e8c11b8ede"
|
||||
"reference": "906a5fafabe5e6ba51ef3dc65b2722a677908837"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/7c751ea006577e4c2e83326d90c8b1e8c11b8ede",
|
||||
"reference": "7c751ea006577e4c2e83326d90c8b1e8c11b8ede",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/906a5fafabe5e6ba51ef3dc65b2722a677908837",
|
||||
"reference": "906a5fafabe5e6ba51ef3dc65b2722a677908837",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -5538,7 +5537,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.5"
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -5554,7 +5553,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-02-12T16:18:16+00:00"
|
||||
"time": "2021-03-10T13:58:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pragmarx/google2fa",
|
||||
@ -6097,16 +6096,16 @@
|
||||
},
|
||||
{
|
||||
"name": "psy/psysh",
|
||||
"version": "v0.10.6",
|
||||
"version": "v0.10.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bobthecow/psysh.git",
|
||||
"reference": "6f990c19f91729de8b31e639d6e204ea59f19cf3"
|
||||
"reference": "a395af46999a12006213c0c8346c9445eb31640c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/6f990c19f91729de8b31e639d6e204ea59f19cf3",
|
||||
"reference": "6f990c19f91729de8b31e639d6e204ea59f19cf3",
|
||||
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/a395af46999a12006213c0c8346c9445eb31640c",
|
||||
"reference": "a395af46999a12006213c0c8346c9445eb31640c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -6167,9 +6166,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/bobthecow/psysh/issues",
|
||||
"source": "https://github.com/bobthecow/psysh/tree/v0.10.6"
|
||||
"source": "https://github.com/bobthecow/psysh/tree/v0.10.7"
|
||||
},
|
||||
"time": "2021-01-18T15:53:43+00:00"
|
||||
"time": "2021-03-14T02:14:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ralouphie/getallheaders",
|
||||
@ -7056,16 +7055,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v5.2.4",
|
||||
"version": "v5.2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "d6d0cc30d8c0fda4e7b213c20509b0159a8f4556"
|
||||
"reference": "938ebbadae1b0a9c9d1ec313f87f9708609f1b79"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/d6d0cc30d8c0fda4e7b213c20509b0159a8f4556",
|
||||
"reference": "d6d0cc30d8c0fda4e7b213c20509b0159a8f4556",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/938ebbadae1b0a9c9d1ec313f87f9708609f1b79",
|
||||
"reference": "938ebbadae1b0a9c9d1ec313f87f9708609f1b79",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -7133,7 +7132,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v5.2.4"
|
||||
"source": "https://github.com/symfony/console/tree/v5.2.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -7149,7 +7148,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-02-23T10:08:49+00:00"
|
||||
"time": "2021-03-06T13:42:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
@ -7879,16 +7878,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-kernel",
|
||||
"version": "v5.2.4",
|
||||
"version": "v5.2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-kernel.git",
|
||||
"reference": "c452dbe4f385f030c3957821bf921b13815d6140"
|
||||
"reference": "b8c63ef63c2364e174c3b3e0ba0bf83455f97f73"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/c452dbe4f385f030c3957821bf921b13815d6140",
|
||||
"reference": "c452dbe4f385f030c3957821bf921b13815d6140",
|
||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/b8c63ef63c2364e174c3b3e0ba0bf83455f97f73",
|
||||
"reference": "b8c63ef63c2364e174c3b3e0ba0bf83455f97f73",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -7971,7 +7970,7 @@
|
||||
"description": "Provides a structured process for converting a Request into a Response",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/http-kernel/tree/v5.2.4"
|
||||
"source": "https://github.com/symfony/http-kernel/tree/v5.2.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -7987,20 +7986,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-03-04T18:05:55+00:00"
|
||||
"time": "2021-03-10T17:07:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/mime",
|
||||
"version": "v5.2.4",
|
||||
"version": "v5.2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/mime.git",
|
||||
"reference": "5155d2fe14ef1eb150e3bdbbc1ec1455df95e9cd"
|
||||
"reference": "554ba128f1955038b45db5e1fa7e93bfc683b139"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/mime/zipball/5155d2fe14ef1eb150e3bdbbc1ec1455df95e9cd",
|
||||
"reference": "5155d2fe14ef1eb150e3bdbbc1ec1455df95e9cd",
|
||||
"url": "https://api.github.com/repos/symfony/mime/zipball/554ba128f1955038b45db5e1fa7e93bfc683b139",
|
||||
"reference": "554ba128f1955038b45db5e1fa7e93bfc683b139",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -8011,12 +8010,13 @@
|
||||
"symfony/polyfill-php80": "^1.15"
|
||||
},
|
||||
"conflict": {
|
||||
"egulias/email-validator": "~3.0.0",
|
||||
"phpdocumentor/reflection-docblock": "<3.2.2",
|
||||
"phpdocumentor/type-resolver": "<1.4.0",
|
||||
"symfony/mailer": "<4.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"egulias/email-validator": "^2.1.10",
|
||||
"egulias/email-validator": "^2.1.10|^3.1",
|
||||
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
|
||||
"symfony/dependency-injection": "^4.4|^5.0",
|
||||
"symfony/property-access": "^4.4|^5.1",
|
||||
@ -8053,7 +8053,7 @@
|
||||
"mime-type"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/mime/tree/v5.2.4"
|
||||
"source": "https://github.com/symfony/mime/tree/v5.2.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -8069,7 +8069,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-02-15T18:55:04+00:00"
|
||||
"time": "2021-03-07T16:08:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
@ -9352,16 +9352,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation",
|
||||
"version": "v5.2.4",
|
||||
"version": "v5.2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/translation.git",
|
||||
"reference": "74b0353ab34ff4cca827a2cf909e325d96815e60"
|
||||
"reference": "0947ab1e3aabd22a6bef393874b2555d2bb976da"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/74b0353ab34ff4cca827a2cf909e325d96815e60",
|
||||
"reference": "74b0353ab34ff4cca827a2cf909e325d96815e60",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/0947ab1e3aabd22a6bef393874b2555d2bb976da",
|
||||
"reference": "0947ab1e3aabd22a6bef393874b2555d2bb976da",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -9425,7 +9425,7 @@
|
||||
"description": "Provides tools to internationalize your application",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/translation/tree/v5.2.4"
|
||||
"source": "https://github.com/symfony/translation/tree/v5.2.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -9441,7 +9441,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-03-04T15:41:09+00:00"
|
||||
"time": "2021-03-06T07:59:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation-contracts",
|
||||
@ -9523,16 +9523,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-dumper",
|
||||
"version": "v5.2.4",
|
||||
"version": "v5.2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-dumper.git",
|
||||
"reference": "6a81fec0628c468cf6d5c87a4d003725e040e223"
|
||||
"reference": "002ab5a36702adf0c9a11e6d8836623253e9045e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/6a81fec0628c468cf6d5c87a4d003725e040e223",
|
||||
"reference": "6a81fec0628c468cf6d5c87a4d003725e040e223",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/002ab5a36702adf0c9a11e6d8836623253e9045e",
|
||||
"reference": "002ab5a36702adf0c9a11e6d8836623253e9045e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -9591,7 +9591,7 @@
|
||||
"dump"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/var-dumper/tree/v5.2.4"
|
||||
"source": "https://github.com/symfony/var-dumper/tree/v5.2.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -9607,7 +9607,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-02-18T23:11:19+00:00"
|
||||
"time": "2021-03-06T07:59:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tijsverkoyen/css-to-inline-styles",
|
||||
@ -11150,16 +11150,16 @@
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v2.18.2",
|
||||
"version": "v2.18.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
|
||||
"reference": "18f8c9d184ba777380794a389fabc179896ba913"
|
||||
"reference": "ab99202fccff2a9f97592fbe1b5c76dd06df3513"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/18f8c9d184ba777380794a389fabc179896ba913",
|
||||
"reference": "18f8c9d184ba777380794a389fabc179896ba913",
|
||||
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/ab99202fccff2a9f97592fbe1b5c76dd06df3513",
|
||||
"reference": "ab99202fccff2a9f97592fbe1b5c76dd06df3513",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -11221,6 +11221,7 @@
|
||||
"tests/Test/IntegrationCaseFactoryInterface.php",
|
||||
"tests/Test/InternalIntegrationCaseFactory.php",
|
||||
"tests/Test/IsIdenticalConstraint.php",
|
||||
"tests/Test/TokensWithObservedTransformers.php",
|
||||
"tests/TestCase.php"
|
||||
]
|
||||
},
|
||||
@ -11241,7 +11242,7 @@
|
||||
"description": "A tool to automatically fix PHP code style",
|
||||
"support": {
|
||||
"issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues",
|
||||
"source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v2.18.2"
|
||||
"source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v2.18.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -11249,7 +11250,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2021-01-26T00:22:21+00:00"
|
||||
"time": "2021-03-10T19:39:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "hamcrest/hamcrest-php",
|
||||
@ -13467,16 +13468,16 @@
|
||||
},
|
||||
{
|
||||
"name": "swagger-api/swagger-ui",
|
||||
"version": "v3.44.1",
|
||||
"version": "v3.45.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/swagger-api/swagger-ui.git",
|
||||
"reference": "db830fbb51ba987dc9931172c891b318ba444d39"
|
||||
"reference": "1ba7af074f97c872a64a415e0507c11cf8f3601b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/db830fbb51ba987dc9931172c891b318ba444d39",
|
||||
"reference": "db830fbb51ba987dc9931172c891b318ba444d39",
|
||||
"url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/1ba7af074f97c872a64a415e0507c11cf8f3601b",
|
||||
"reference": "1ba7af074f97c872a64a415e0507c11cf8f3601b",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -13522,9 +13523,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/swagger-api/swagger-ui/issues",
|
||||
"source": "https://github.com/swagger-api/swagger-ui/tree/v3.44.1"
|
||||
"source": "https://github.com/swagger-api/swagger-ui/tree/v3.45.0"
|
||||
},
|
||||
"time": "2021-03-04T18:13:06+00:00"
|
||||
"time": "2021-03-11T17:20:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/debug",
|
||||
@ -13727,16 +13728,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v5.2.4",
|
||||
"version": "v5.2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "7d6ae0cce3c33965af681a4355f1c4de326ed277"
|
||||
"reference": "298a08ddda623485208506fcee08817807a251dd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/7d6ae0cce3c33965af681a4355f1c4de326ed277",
|
||||
"reference": "7d6ae0cce3c33965af681a4355f1c4de326ed277",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/298a08ddda623485208506fcee08817807a251dd",
|
||||
"reference": "298a08ddda623485208506fcee08817807a251dd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -13782,7 +13783,7 @@
|
||||
"description": "Loads and dumps YAML files",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/yaml/tree/v5.2.4"
|
||||
"source": "https://github.com/symfony/yaml/tree/v5.2.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -13798,7 +13799,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-02-22T15:48:39+00:00"
|
||||
"time": "2021-03-06T07:59:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "theseer/tokenizer",
|
||||
@ -14080,7 +14081,6 @@
|
||||
"aliases": [],
|
||||
"minimum-stability": "dev",
|
||||
"stability-flags": {
|
||||
"dacastro4/laravel-gmail": 20,
|
||||
"webpatser/laravel-countries": 20
|
||||
},
|
||||
"prefer-stable": true,
|
||||
|
@ -13,7 +13,7 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', ''),
|
||||
'app_version' => '5.1.24',
|
||||
'app_version' => '5.1.29',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', false),
|
||||
@ -143,4 +143,6 @@ return [
|
||||
'v4_migration_version' => '4.5.31',
|
||||
'flutter_canvas_kit' => env('FLUTTER_CANVAS_KIT', false),
|
||||
'webcron_secret' => env('WEBCRON_SECRET', false),
|
||||
'disable_auto_update' => env('DISABLE_AUTO_UPDATE', false),
|
||||
'invoiceninja_hosted_pdf_generation' => env('NINJA_HOSTED_PDF', false),
|
||||
];
|
||||
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class Change2faColumnFromVarcharToText extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->text('google_2fa_secret')->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddUniqueConstraintsOnAllEntities extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('expenses', function (Blueprint $table) {
|
||||
$table->unique(['company_id', 'number']);
|
||||
});
|
||||
|
||||
Schema::table('tasks', function (Blueprint $table) {
|
||||
$table->unique(['company_id', 'number']);
|
||||
});
|
||||
|
||||
Schema::table('vendors', function (Blueprint $table) {
|
||||
$table->unique(['company_id', 'number']);
|
||||
});
|
||||
|
||||
Schema::table('payments', function (Blueprint $table) {
|
||||
$table->unique(['company_id', 'number']);
|
||||
});
|
||||
|
||||
Schema::table('projects', function (Blueprint $table) {
|
||||
$table->unique(['company_id', 'number']);
|
||||
});
|
||||
|
||||
Schema::table('clients', function (Blueprint $table) {
|
||||
$table->unique(['company_id', 'number']);
|
||||
});
|
||||
|
||||
Schema::table('payment_hashes', function (Blueprint $table) {
|
||||
$table->unique(['hash']);
|
||||
});
|
||||
|
||||
Schema::table('recurring_invoices', function (Blueprint $table) {
|
||||
$table->string('number')->change();
|
||||
$table->unique(['company_id', 'number']);
|
||||
});
|
||||
|
||||
Schema::table('recurring_invoice_invitations', function (Blueprint $table) {
|
||||
$table->unique(['client_contact_id', 'recurring_invoice_id'],'recur_invoice_client_unique');
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddInvoiceIdToClientSubscriptionsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('client_subscriptions', function (Blueprint $table) {
|
||||
$table->unsignedInteger('invoice_id')->nullable();
|
||||
$table->unsignedInteger('quantity')->default(1);
|
||||
$table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade')->onUpdate('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
}
|
||||
}
|
2
public/css/app.css
vendored
2
public/css/app.css
vendored
File diff suppressed because one or more lines are too long
2
public/flutter_service_worker.js
vendored
2
public/flutter_service_worker.js
vendored
@ -30,7 +30,7 @@ const RESOURCES = {
|
||||
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "3e722fd57a6db80ee119f0e2c230ccff",
|
||||
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
|
||||
"/": "23224b5e03519aaa87594403d54412cf",
|
||||
"main.dart.js": "c11c4d2efa9e671a88eb792d18c2296e",
|
||||
"main.dart.js": "114d8affe0f4b7576170753cf9fb4c0a",
|
||||
"version.json": "b7c8971e1ab5b627fd2a4317c52b843e",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b"
|
||||
};
|
||||
|
212263
public/main.dart.js
vendored
212263
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
|
||||
"/css/app.css": "/css/app.css?id=745170b7d7a4dc7469f2",
|
||||
"/css/app.css": "/css/app.css?id=e8d6d5e8cb60bc2f15b3",
|
||||
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4",
|
||||
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
|
||||
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",
|
||||
|
@ -1976,7 +1976,7 @@ $LANG = array(
|
||||
'require_invoice_signature_help' => 'Require client to provide their signature.',
|
||||
'require_quote_signature' => 'Quote Signature',
|
||||
'require_quote_signature_help' => 'Require client to provide their signature.',
|
||||
'i_agree' => 'I Agree To',
|
||||
'i_agree' => 'I Agree To The Terms',
|
||||
'sign_here' => 'Please sign here:',
|
||||
'authorization' => 'Authorization',
|
||||
'signed' => 'Signed',
|
||||
@ -3449,10 +3449,10 @@ $LANG = array(
|
||||
'client_country' => 'Client Country',
|
||||
'client_is_active' => 'Client is Active',
|
||||
'client_balance' => 'Client Balance',
|
||||
'client_address1' => 'Client Address 1',
|
||||
'client_address2' => 'Client Address 2',
|
||||
'client_shipping_address1' => 'Client Shipping Address 1',
|
||||
'client_shipping_address2' => 'Client Shipping Address 2',
|
||||
'client_address1' => 'Client Street',
|
||||
'client_address2' => 'Client Apt/Suite',
|
||||
'client_shipping_address1' => 'Client Shipping Street',
|
||||
'client_shipping_address2' => 'Client Shipping Apt/Suite',
|
||||
'tax_rate1' => 'Tax Rate 1',
|
||||
'tax_rate2' => 'Tax Rate 2',
|
||||
'tax_rate3' => 'Tax Rate 3',
|
||||
@ -3532,8 +3532,8 @@ $LANG = array(
|
||||
'marked_credit_as_sent' => 'Successfully marked credit as sent',
|
||||
'email_subject_payment_partial' => 'Email Partial Payment Subject',
|
||||
'is_approved' => 'Is Approved',
|
||||
'migration_went_wrong' => 'Oops, something went wrong! Make sure you did proper setup with V2 of Invoice Ninja, before starting migration.',
|
||||
'cross_migration_message' => 'Cross account migration is not allowed. Please read more about it here: <a href="https://invoiceninja.github.io/cross-site-migration.html">https://invoiceninja.github.io/cross-site-migration.html</a>',
|
||||
'migration_went_wrong' => 'Oops, something went wrong! Please make sure you have setup an Invoice Ninja v5 instance before starting the migration.',
|
||||
'cross_migration_message' => 'Cross account migration is not allowed. Please read more about it here: <a href="https://invoiceninja.github.io/docs/migration/#troubleshooting">https://invoiceninja.github.io/docs/migration/#troubleshooting</a>',
|
||||
'email_credit' => 'Email Credit',
|
||||
'client_email_not_set' => 'Client does not have an email address set',
|
||||
'ledger' => 'Ledger',
|
||||
@ -3912,7 +3912,7 @@ $LANG = array(
|
||||
'show' => 'Show',
|
||||
'empty_columns' => 'Empty Columns',
|
||||
'project_name' => 'Project Name',
|
||||
'counter_pattern_error' => 'To use :client_counter please add either :number or :id_number to prevent conflicts',
|
||||
'counter_pattern_error' => 'To use :client_counter please add either :client_number or :client_id_number to prevent conflicts',
|
||||
'this_quarter' => 'This Quarter',
|
||||
'to_update_run' => 'To update run',
|
||||
'registration_url' => 'Registration URL',
|
||||
@ -3968,8 +3968,8 @@ $LANG = array(
|
||||
'list_of_recurring_invoices' => 'List of recurring invoices',
|
||||
'details_of_recurring_invoice' => 'Here are some details about recurring invoice',
|
||||
'cancellation' => 'Cancellation',
|
||||
'about_cancellation' => 'In case you want to stop the recurring invoice, please click the request the cancellation.',
|
||||
'cancellation_warning' => 'Warning! You are requesting a cancellation of this service. Your service may be cancelled with no further notification to you.',
|
||||
'about_cancellation' => 'In case you want to stop the recurring invoice,\n please click the request the cancellation.',
|
||||
'cancellation_warning' => 'Warning! You are requesting a cancellation of this service.\n Your service may be cancelled with no further notification to you.',
|
||||
'cancellation_pending' => 'Cancellation pending, we\'ll be in touch!',
|
||||
'list_of_payments' => 'List of payments',
|
||||
'payment_details' => 'Details of the payment',
|
||||
@ -4135,19 +4135,26 @@ $LANG = array(
|
||||
'payment_message_extended' => 'Thank you for your payment of :amount for :invoice',
|
||||
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is bigger than $1 or currency equivalent.',
|
||||
'payment_token_not_found' => 'Payment token not found, please try again. If an issue still persist, try with another payment method',
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
'vendor_address1' => 'Vendor Street',
|
||||
'vendor_address2' => 'Vendor Apt/Suite',
|
||||
'partially_unapplied' => 'Partially Unapplied',
|
||||
'select_a_gmail_user' => 'Please select a user authenticated with Gmail',
|
||||
'list_long_press' => 'List Long Press',
|
||||
'show_actions' => 'Show Actions',
|
||||
'start_multiselect' => 'Start Multiselect',
|
||||
'email_sent_to_confirm_email' => 'An email has been sent to confirm the email address',
|
||||
'converted_paid_to_date' => 'Converted Paid to Date',
|
||||
'converted_credit_balance' => 'Converted Credit Balance',
|
||||
'converted_total' => 'Converted Total',
|
||||
'reply_to_name' => 'Reply-To Name',
|
||||
'payment_status_-2' => 'Partially Unapplied',
|
||||
'color_theme' => 'Color Theme',
|
||||
'start_migration' => 'Start Migration',
|
||||
'recurring_cancellation_request' => 'Request for recurring invoice cancellation from :contact',
|
||||
'recurring_cancellation_request_body' => ':contact from Client :client requested to cancel Recurring Invoice :invoice',
|
||||
'hello' => 'Hello',
|
||||
'group_documents' => 'Group documents',
|
||||
'quote_approval_confirmation_label' => 'Are you sure you want to approve this quote?',
|
||||
|
||||
'click_agree_to_accept_terms' => 'Click "Agree" to Accept Terms.',
|
||||
'agree' => 'Agree',
|
||||
|
||||
'pending_approval' => 'Pending Approval',
|
||||
'migration_select_company_label' => 'Select companies to migrate',
|
||||
'force_migration' => 'Force migration',
|
||||
'require_password_with_social_login' => 'Require Password with Social Login',
|
||||
@ -4167,6 +4174,32 @@ $LANG = array(
|
||||
'zoho' => 'Zoho',
|
||||
'accounting' => 'Accounting',
|
||||
'required_files_missing' => 'Please provide all CSVs.',
|
||||
'migration_auth_label' => 'Let\'s continue by authenticating.',
|
||||
'api_secret' => 'API secret',
|
||||
'migration_api_secret_notice' => 'You can find API_SECRET in the .env file or Invoice Ninja v5. If property is missing, leave field blank.',
|
||||
'use_last_email' => 'Use last email',
|
||||
'activate_company' => 'Activate Company',
|
||||
'activate_company_help' => 'Enable emails, recurring invoices and notifications',
|
||||
'an_error_occurred_try_again' => 'An error occurred, please try again',
|
||||
'please_first_set_a_password' => 'Please first set a password',
|
||||
'changing_phone_disables_two_factor' => 'Warning: Changing your phone number will disable 2FA',
|
||||
'help_translate' => 'Help Translate',
|
||||
'please_select_a_country' => 'Please select a country',
|
||||
'disabled_two_factor' => 'Successfully disabled 2FA',
|
||||
'connected_google' => 'Successfully connected account',
|
||||
'disconnected_google' => 'Successfully disconnected account',
|
||||
'delivered' => 'Delivered',
|
||||
'spam' => 'Spam',
|
||||
'view_docs' => 'View Docs',
|
||||
'enter_phone_to_enable_two_factor' => 'Please provide a mobile phone number to enable two factor authentication',
|
||||
'send_sms' => 'Send SMS',
|
||||
'sms_code' => 'SMS Code',
|
||||
'connect_google' => 'Connect Google',
|
||||
'disconnect_google' => 'Disconnect Google',
|
||||
'disable_two_factor' => 'Disable Two Factor',
|
||||
'invoice_task_datelog' => 'Invoice Task Datelog',
|
||||
'invoice_task_datelog_help' => 'Add date details to the invoice line items',
|
||||
'promo_code' => 'Promo code',
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
17
resources/views/billing-portal/purchase.blade.php
Normal file
17
resources/views/billing-portal/purchase.blade.php
Normal file
@ -0,0 +1,17 @@
|
||||
@extends('portal.ninja2020.layout.clean')
|
||||
@section('meta_title', $billing_subscription->product->product_key)
|
||||
|
||||
@section('body')
|
||||
@livewire('billing-portal-purchase', ['billing_subscription' => $billing_subscription, 'contact' => auth('contact')->user(), 'hash' => $hash])
|
||||
@stop
|
||||
|
||||
@push('footer')
|
||||
<script>
|
||||
function updateGatewayFields(companyGatewayId, paymentMethodId) {
|
||||
document.getElementById('company_gateway_id').value = companyGatewayId;
|
||||
document.getElementById('payment_method_id').value = paymentMethodId;
|
||||
}
|
||||
|
||||
Livewire.on('beforePaymentEventsCompleted', () => document.getElementById('payment-method-form').submit());
|
||||
</script>
|
||||
@endpush
|
@ -10,7 +10,7 @@
|
||||
<br>
|
||||
<br>
|
||||
<p>
|
||||
{{ $signature }}
|
||||
{!! $signature !!}
|
||||
</p>
|
||||
@endif
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
<br>
|
||||
<br>
|
||||
<p>
|
||||
{{ $signature }}
|
||||
{!! $signature !!}
|
||||
</p>
|
||||
@endif
|
||||
|
||||
|
@ -27,7 +27,7 @@
|
||||
@if($signature)
|
||||
<tr>
|
||||
<td>
|
||||
<p>{{ $signature }}</p>
|
||||
<p>{!! $signature !!}</p>
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
|
@ -262,7 +262,7 @@
|
||||
|
||||
<div class="contacts-wrapper">
|
||||
<div class="contact-wrapper-left-side">
|
||||
<p class="contact-label">$to_label:</p>
|
||||
<p class="contact-label">$from_label:</p>
|
||||
<div class="company-info">
|
||||
<div id="company-details"></div>
|
||||
<div id="company-address"></div>
|
||||
@ -270,7 +270,7 @@
|
||||
</div>
|
||||
|
||||
<div class="contact-wrapper-right-side">
|
||||
<p class="contact-label">$from_label:</p>
|
||||
<p class="contact-label">$to_label:</p>
|
||||
<div id="client-details"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -8,7 +8,9 @@
|
||||
@include('portal.ninja2020.components.general.sidebar.mobile')
|
||||
|
||||
<!-- Static sidebar for desktop -->
|
||||
@include('portal.ninja2020.components.general.sidebar.desktop')
|
||||
@unless(request()->query('sidebar') === 'hidden')
|
||||
@include('portal.ninja2020.components.general.sidebar.desktop')
|
||||
@endunless
|
||||
|
||||
<div class="flex flex-col w-0 flex-1 overflow-hidden">
|
||||
@include('portal.ninja2020.components.general.sidebar.header')
|
||||
@ -34,4 +36,4 @@
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
@ -0,0 +1,127 @@
|
||||
<div class="grid grid-cols-12">
|
||||
<div class="col-span-12 lg:col-span-6 bg-gray-50 shadow-lg lg:h-screen flex flex-col items-center">
|
||||
<div class="w-full p-10 lg:w-1/2 lg:mt-48 lg:p-0">
|
||||
<img class="h-8" src="{{ $billing_subscription->company->present()->logo }}"
|
||||
alt="{{ $billing_subscription->company->present()->name }}">
|
||||
|
||||
<h1 id="billing-page-company-logo" class="text-3xl font-bold tracking-wide mt-8">
|
||||
{{ $billing_subscription->product->product_key }}
|
||||
</h1>
|
||||
|
||||
<p class="my-6">{{ $billing_subscription->product->notes }}</p>
|
||||
|
||||
<span class="text-sm uppercase font-bold">{{ ctrans('texts.total') }}:</span>
|
||||
|
||||
<h1 class="text-2xl font-bold tracking-wide">{{ App\Utils\Number::formatMoney($billing_subscription->product->price, $billing_subscription->company) }}</h1>
|
||||
|
||||
@if(auth('contact')->user())
|
||||
<a href="{{ route('client.invoices.index') }}" class="block mt-16 inline-flex items-center space-x-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-arrow-left">
|
||||
<line x1="19" y1="12" x2="5" y2="12"></line>
|
||||
<polyline points="12 19 5 12 12 5"></polyline>
|
||||
</svg>
|
||||
|
||||
<span>{{ ctrans('texts.client_portal') }}</span>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 lg:col-span-6 bg-white lg:shadow-lg lg:h-screen">
|
||||
<div class="grid grid-cols-12 flex flex-col p-10 lg:mt-48 lg:ml-16">
|
||||
<div class="col-span-12 w-full lg:col-span-6">
|
||||
<h2 class="text-2xl font-bold tracking-wide">{{ $heading_text }}</h2>
|
||||
@if (session()->has('message'))
|
||||
@component('portal.ninja2020.components.message')
|
||||
{{ session('message') }}
|
||||
@endcomponent
|
||||
@endif
|
||||
|
||||
@if($this->steps['fetched_payment_methods'])
|
||||
<div class="flex items-center mt-4 text-sm">
|
||||
<form action="{{ route('client.payments.process', ['hash' => $hash, 'sidebar' => 'hidden']) }}"
|
||||
method="post"
|
||||
id="payment-method-form">
|
||||
@csrf
|
||||
|
||||
@if($invoice instanceof \App\Models\Invoice)
|
||||
<input type="hidden" name="invoices[]" value="{{ $invoice->hashed_id }}">
|
||||
<input type="hidden" name="payable_invoices[0][amount]"
|
||||
value="{{ $invoice->partial > 0 ? \App\Utils\Number::formatValue($invoice->partial, $invoice->client->currency()) : \App\Utils\Number::formatValue($invoice->balance, $invoice->client->currency()) }}">
|
||||
<input type="hidden" name="payable_invoices[0][invoice_id]"
|
||||
value="{{ $invoice->hashed_id }}">
|
||||
@endif
|
||||
|
||||
<input type="hidden" name="action" value="payment">
|
||||
<input type="hidden" name="company_gateway_id" value="{{ $company_gateway_id }}"/>
|
||||
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}"/>
|
||||
</form>
|
||||
|
||||
@foreach($this->methods as $method)
|
||||
<button
|
||||
wire:click="handleMethodSelectingEvent('{{ $method['company_gateway_id'] }}', '{{ $method['gateway_type_id'] }}')"
|
||||
class="px-3 py-2 border rounded mr-4 hover:border-blue-600">
|
||||
{{ $method['label'] }}
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<form wire:submit.prevent="authenticate" class="mt-8">
|
||||
@csrf
|
||||
|
||||
<label for="email_address">
|
||||
<span class="input-label">{{ ctrans('texts.email_address') }}</span>
|
||||
<input wire:model.defer="email" type="email" class="input w-full"/>
|
||||
|
||||
@error('email')
|
||||
<p class="validation validation-fail block w-full" role="alert">
|
||||
{{ $message }}
|
||||
</p>
|
||||
@enderror
|
||||
</label>
|
||||
|
||||
@if($steps['existing_user'])
|
||||
<label for="password" class="block mt-2">
|
||||
<span class="input-label">{{ ctrans('texts.password') }}</span>
|
||||
<input wire:model.defer="password" type="password" class="input w-full" autofocus/>
|
||||
|
||||
@error('password')
|
||||
<p class="validation validation-fail block w-full" role="alert">
|
||||
{{ $message }}
|
||||
</p>
|
||||
@enderror
|
||||
</label>
|
||||
@endif
|
||||
|
||||
<button type="submit"
|
||||
class="button button-block bg-primary text-white mt-4">{{ ctrans('texts.next') }}</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
<div class="relative mt-8">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
|
||||
<div class="relative flex justify-center text-sm leading-5">
|
||||
<span class="px-2 text-gray-700 bg-white">Have a coupon code?</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form wire:submit.prevent="applyCouponCode" class="mt-4">
|
||||
@csrf
|
||||
|
||||
<div class="flex items-center">
|
||||
<label class="w-full mr-2">
|
||||
<input type="text" wire:model.defer="coupon" class="input w-full m-0" />
|
||||
</label>
|
||||
|
||||
<button class="button bg-primary m-0 text-white">{{ ctrans('texts.apply') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -63,6 +63,8 @@
|
||||
{{-- Feel free to push anything to header using @push('header') --}}
|
||||
@stack('head')
|
||||
|
||||
@livewireStyles
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/cookieconsent@3/build/cookieconsent.min.css" />
|
||||
</head>
|
||||
|
||||
@ -77,6 +79,8 @@
|
||||
|
||||
@yield('body')
|
||||
|
||||
@livewireScripts
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/cookieconsent@3/build/cookieconsent.min.js" data-cfasync="false"></script>
|
||||
<script>
|
||||
window.addEventListener("load", function(){
|
||||
|
@ -31,10 +31,13 @@
|
||||
<div>
|
||||
@yield('gateway_content')
|
||||
</div>
|
||||
<span class="block mx-4 mb-4 text-xs inline-flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-green-600"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>
|
||||
<span class="ml-1">Secure 256-bit encryption</span>
|
||||
</span>
|
||||
|
||||
@if(Request::isSecure())
|
||||
<span class="block mx-4 mb-4 text-xs inline-flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-green-600"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>
|
||||
<span class="ml-1">Secure 256-bit encryption</span>
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
@ -20,7 +20,7 @@
|
||||
-- Commands to create a MySQL database and user
|
||||
CREATE SCHEMA `db-ninja-01` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
|
||||
CREATE USER 'ninja'@'localhost' IDENTIFIED BY 'ninja';
|
||||
GRANT ALL PRIVILEGES ON `ninja`.* TO 'ninja'@'localhost';
|
||||
GRANT ALL PRIVILEGES ON `db-ninja-01`.* TO 'ninja'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
</pre>
|
||||
</details>
|
||||
|
@ -76,6 +76,8 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence
|
||||
Route::get('logout', 'Auth\ContactLoginController@logout')->name('logout');
|
||||
});
|
||||
|
||||
Route::get('client/subscription/{billing_subscription}/purchase', 'ClientPortal\BillingSubscriptionPurchaseController@index')->name('client.subscription.purchase');
|
||||
|
||||
Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'client.'], function () {
|
||||
/*Invitation catches*/
|
||||
Route::get('recurring_invoice/{invitation_key}', 'ClientPortal\InvitationController@recurringRouter');
|
||||
|
@ -54,6 +54,28 @@ class ClientApiTest extends TestCase
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testDuplicateNumberCatch()
|
||||
{
|
||||
$data = [
|
||||
'name' => $this->faker->firstName,
|
||||
'number' => 'iamaduplicate',
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/clients', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/clients', $data);
|
||||
|
||||
$response->assertStatus(302);
|
||||
}
|
||||
|
||||
public function testClientPut()
|
||||
{
|
||||
$data = [
|
||||
@ -67,6 +89,20 @@ class ClientApiTest extends TestCase
|
||||
])->put('/api/v1/clients/'.$this->encodePrimaryKey($this->client->id), $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/clients/'.$this->encodePrimaryKey($this->client->id), $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/clients/', $data);
|
||||
|
||||
$response->assertStatus(302);
|
||||
}
|
||||
|
||||
public function testClientGet()
|
||||
|
@ -131,4 +131,80 @@ class CreditTest extends TestCase
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testDuplicateNumberCatch()
|
||||
{
|
||||
$data = [
|
||||
'status_id' => 1,
|
||||
'number' => 'dfdfd',
|
||||
'discount' => 0,
|
||||
'is_amount_discount' => 1,
|
||||
'number' => '3434343',
|
||||
'public_notes' => 'notes',
|
||||
'is_deleted' => 0,
|
||||
'custom_value1' => 0,
|
||||
'custom_value2' => 0,
|
||||
'custom_value3' => 0,
|
||||
'custom_value4' => 0,
|
||||
'status' => 1,
|
||||
'client_id' => $this->encodePrimaryKey($this->client->id),
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/credits', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/credits', $data);
|
||||
|
||||
$response->assertStatus(302);
|
||||
}
|
||||
|
||||
public function testCreditPut()
|
||||
{
|
||||
$data = [
|
||||
'status_id' => 1,
|
||||
'number' => 'dfdfd',
|
||||
'discount' => 0,
|
||||
'is_amount_discount' => 1,
|
||||
'number' => '3434343',
|
||||
'public_notes' => 'notes',
|
||||
'is_deleted' => 0,
|
||||
'custom_value1' => 0,
|
||||
'custom_value2' => 0,
|
||||
'custom_value3' => 0,
|
||||
'custom_value4' => 0,
|
||||
'status' => 1,
|
||||
'client_id' => $this->encodePrimaryKey($this->client->id),
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/credits/'.$this->encodePrimaryKey($this->credit->id), $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/credits/'.$this->encodePrimaryKey($this->credit->id), $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/credits/', $data);
|
||||
|
||||
$response->assertStatus(302);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user