mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
fix conflicts
This commit is contained in:
commit
f80179814d
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.27
|
||||
5.1.29
|
@ -101,6 +101,7 @@ class CreateSingleAccount extends Command
|
||||
$company = Company::factory()->create([
|
||||
'account_id' => $account->id,
|
||||
'slack_webhook_url' => config('ninja.notification.slack'),
|
||||
'default_password_timeout' => 30*60000,
|
||||
]);
|
||||
|
||||
$account->default_company_id = $company->id;
|
||||
|
@ -58,23 +58,16 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
$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 --daemon')->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();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -35,12 +35,18 @@ class WebhookConfiguration
|
||||
*/
|
||||
public $post_purchase_body = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $post_purchase_rest_method = 'POST';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $casts = [
|
||||
'return_url' => 'string',
|
||||
'post_purchase_url' => 'string',
|
||||
'post_purchase_rest_method' => 'string',
|
||||
'post_purchase_headers' => 'array',
|
||||
'post_purchase_body' => 'object',
|
||||
];
|
||||
|
@ -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',
|
||||
|
@ -37,6 +37,7 @@ class CompanyFactory
|
||||
$company->enabled_modules = config('ninja.enabled_modules'); //32767;//8191; //4095
|
||||
$company->default_password_timeout = 1800000;
|
||||
|
||||
|
||||
return $company;
|
||||
}
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ class LoginController extends BaseController
|
||||
{
|
||||
$google2fa = new Google2FA();
|
||||
|
||||
if(!$google2fa->verifyKey(decrypt($user->google_2fa_secret), $request->input('one_time_password')))
|
||||
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)
|
||||
@ -194,6 +194,7 @@ class LoginController extends BaseController
|
||||
|
||||
$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()
|
||||
@ -322,33 +323,35 @@ class LoginController extends BaseController
|
||||
|
||||
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
|
||||
|
||||
// $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'));
|
||||
|
||||
$token = false;
|
||||
|
||||
try{
|
||||
$token = $client->authenticate(request()->input('server_auth_code'));
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
// 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'));
|
||||
// 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 = '';
|
||||
|
||||
if (array_key_exists('refresh_token', $token)) {
|
||||
$refresh_token = $token['refresh_token'];
|
||||
}
|
||||
|
||||
//$access_token = $token['access_token'];
|
||||
|
||||
$name = OAuth::splitName($google->harvestName($user));
|
||||
|
||||
$new_account = [
|
||||
|
@ -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()
|
||||
{
|
||||
@ -104,7 +104,6 @@ class ConnectedAccountController extends BaseController
|
||||
}
|
||||
|
||||
$connected_account = [
|
||||
'password' => '',
|
||||
'email' => $google->harvestEmail($user),
|
||||
'oauth_user_id' => $google->harvestSubField($user),
|
||||
'oauth_user_token' => $token,
|
||||
@ -117,11 +116,8 @@ class ConnectedAccountController extends BaseController
|
||||
auth()->user()->email_verified_at = now();
|
||||
auth()->user()->save();
|
||||
|
||||
//$ct = CompanyUser::whereUserId(auth()->user()->id);
|
||||
//return $this->listResponse($ct);
|
||||
|
||||
return $this->itemResponse(auth()->user());
|
||||
// return $this->listResponse(auth()->user());
|
||||
|
||||
}
|
||||
|
||||
return response()
|
||||
|
@ -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')) {
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
@ -52,7 +53,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'] = [
|
||||
@ -70,6 +70,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;
|
||||
}
|
||||
|
||||
@ -126,7 +130,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,9 +35,8 @@ 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'));
|
||||
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
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']),
|
||||
|
@ -37,6 +37,7 @@ class Project extends BaseModel
|
||||
'custom_value4',
|
||||
'assigned_user_id',
|
||||
'color',
|
||||
'number',
|
||||
];
|
||||
|
||||
public function getEntityType()
|
||||
|
@ -241,7 +241,7 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
|
||||
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));
|
||||
|
||||
(new BillingSubscriptionService)->completePurchase($this->payment_hash);
|
||||
//(new BillingSubscriptionService)->completePurchase($this->payment_hash);
|
||||
|
||||
return $payment->service()->applyNumber()->save();
|
||||
}
|
||||
|
@ -11,6 +11,6 @@ class CreditPolicy extends EntityPolicy
|
||||
|
||||
public function create(User $user) : bool
|
||||
{
|
||||
return $user->isAdmin() || $user->hasPermission('create_quote') || $user->hasPermission('create_all');
|
||||
return $user->isAdmin() || $user->hasPermission('create_credit') || $user->hasPermission('create_all');
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,6 @@ class TaskPolicy extends EntityPolicy
|
||||
{
|
||||
public function create(User $user) : bool
|
||||
{
|
||||
return $user->isAdmin();
|
||||
return $user->isAdmin() || $user->hasPermission('create_task') || $user->hasPermission('create_all');
|
||||
}
|
||||
}
|
||||
|
@ -13,17 +13,25 @@ namespace App\Services\BillingSubscription;
|
||||
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\BillingSubscription;
|
||||
use App\Models\ClientSubscription;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\Product;
|
||||
use App\Models\SystemLog;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
|
||||
class BillingSubscriptionService
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
/** @var BillingSubscription */
|
||||
private $billing_subscription;
|
||||
|
||||
private $client_subscription;
|
||||
|
||||
public function __construct(BillingSubscription $billing_subscription)
|
||||
{
|
||||
$this->billing_subscription = $billing_subscription;
|
||||
@ -56,7 +64,7 @@ class BillingSubscriptionService
|
||||
{
|
||||
$invoice_repo = new InvoiceRepository();
|
||||
|
||||
$data['line_items'] = $this->createLineItems($data['quantity']);
|
||||
$data['line_items'] = $this->createLineItems($data);
|
||||
|
||||
/*
|
||||
If trial_enabled -> return early
|
||||
@ -74,48 +82,154 @@ class BillingSubscriptionService
|
||||
|
||||
}
|
||||
|
||||
private function createLineItems($quantity): array
|
||||
/**
|
||||
* Creates the required line items for the invoice
|
||||
* for the billing subscription.
|
||||
*/
|
||||
private function createLineItems($data): array
|
||||
{
|
||||
$line_items = [];
|
||||
|
||||
$product = $this->billing_subscription->product;
|
||||
|
||||
$item = new InvoiceItem;
|
||||
$item->quantity = $quantity;
|
||||
$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 convertInvoiceToRecurring()
|
||||
/**
|
||||
* If a coupon is entered (and is valid)
|
||||
* then we apply the coupon discount with a line item.
|
||||
*/
|
||||
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($payment_hash)
|
||||
{
|
||||
//The first invoice is a plain invoice - the second is fired on the recurring schedule.
|
||||
}
|
||||
|
||||
public function createClientSubscription($payment_hash, $recurring_invoice_id = null)
|
||||
public function createClientSubscription($payment_hash)
|
||||
{
|
||||
//create the client sub record
|
||||
//create the client subscription record
|
||||
|
||||
//are we in a trial phase?
|
||||
//has money been paid?
|
||||
//is this a recurring or one off subscription.
|
||||
|
||||
//?trial enabled?
|
||||
$cs = new ClientSubscription();
|
||||
$cs->subscription_id = $this->billing_subscription->id;
|
||||
$cs->company_id = $this->billing_subscription->company_id;
|
||||
|
||||
//if is_trial
|
||||
//$cs->trial_started = time();
|
||||
//$cs->trial_duration = time() + duration period in seconds
|
||||
|
||||
//trials will not have any monies paid.
|
||||
|
||||
//if a payment has been made
|
||||
//$cs->invoice_id = xx
|
||||
|
||||
//if is_recurring
|
||||
//create recurring invoice from invoice
|
||||
$recurring_invoice = $this->convertInvoiceToRecurring($payment_hash);
|
||||
$recurring_invoice->frequency_id = $this->billing_subscription->frequency_id;
|
||||
$recurring_invoice->next_send_date = $recurring_invoice->nextDateByFrequency(now()->format('Y-m-d'));
|
||||
//$cs->recurring_invoice_id = $recurring_invoice->id;
|
||||
|
||||
//?set the recurring invoice as active - set the date here also based on the frequency?
|
||||
|
||||
//$cs->quantity = xx
|
||||
|
||||
// client_id
|
||||
//$cs->client_id = xx
|
||||
|
||||
$cs->save();
|
||||
|
||||
$this->client_subscription = $cs;
|
||||
|
||||
}
|
||||
|
||||
public function triggerWebhook($payment_hash)
|
||||
{
|
||||
//hit the webhook to after a successful onboarding
|
||||
//$client = xxxxxxx
|
||||
//todo webhook
|
||||
|
||||
$body = [
|
||||
'billing_subscription' => $this->billing_subscription,
|
||||
'client_subscription' => $this->client_subscription,
|
||||
// 'client' => $client->toArray(),
|
||||
];
|
||||
|
||||
|
||||
$client = new \GuzzleHttp\Client(['headers' => $this->billing_subscription->webhook_configuration->post_purchase_headers]);
|
||||
|
||||
$response = $client->{$this->billing_subscription->webhook_configuration->post_purchase_rest_method}($this->billing_subscription->post_purchase_url,[
|
||||
RequestOptions::JSON => ['body' => $body]
|
||||
]);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$body,
|
||||
SystemLog::CATEGORY_WEBHOOK,
|
||||
SystemLog::EVENT_WEBHOOK_RESPONSE,
|
||||
SystemLog::TYPE_WEBHOOK_RESPONSE,
|
||||
//$client,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public function fireNotifications()
|
||||
|
@ -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 {
|
||||
|
@ -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)) {
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
|
@ -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.27',
|
||||
'app_version' => '5.1.29',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', false),
|
||||
|
@ -37,6 +37,7 @@ class CompanyFactory extends Factory
|
||||
'db' => config('database.default'),
|
||||
'settings' => CompanySettings::defaults(),
|
||||
'is_large' => false,
|
||||
'default_password_timeout' => 30*60000,
|
||||
'enabled_modules' => config('ninja.enabled_modules'),
|
||||
'custom_fields' => (object) [
|
||||
//'invoice1' => 'Custom Date|date',
|
||||
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
4
public/flutter_service_worker.js
vendored
4
public/flutter_service_worker.js
vendored
@ -4,7 +4,7 @@ const TEMP = 'flutter-temp-cache';
|
||||
const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"manifest.json": "77215c1737c7639764e64a192be2f7b8",
|
||||
"manifest.json": "ce1b79950eb917ea619a0a30da27c6a3",
|
||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"assets/NOTICES": "e80e999afd09f0f14597c78d582d9c7c",
|
||||
@ -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": "1edd6ac83b22ed1c401a76cefe7eaa7d",
|
||||
"main.dart.js": "a15d902ac164ae4f5ec2bcdf3470d7e0",
|
||||
"version.json": "b7c8971e1ab5b627fd2a4317c52b843e",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b"
|
||||
};
|
||||
|
189213
public/main.dart.js
vendored
189213
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -11,7 +11,6 @@
|
||||
"related_applications": [
|
||||
{
|
||||
"platform": "play",
|
||||
"url": "https://play.google.com/store/apps/details?id=com.invoiceninja.app",
|
||||
"id": "com.invoiceninja.app"
|
||||
}, {
|
||||
"platform": "itunes",
|
||||
|
@ -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',
|
||||
@ -4170,6 +4177,29 @@ $LANG = array(
|
||||
'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;
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -57,11 +57,34 @@ class ExpenseApiTest extends TestCase
|
||||
$this->assertNotEmpty($arr['data']['number']);
|
||||
}
|
||||
|
||||
public function testDuplicateNumberCatch()
|
||||
{
|
||||
$data = [
|
||||
'public_notes' => $this->faker->firstName,
|
||||
'number' => 'iamaduplicate',
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/expenses', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/expenses', $data);
|
||||
|
||||
$response->assertStatus(302);
|
||||
}
|
||||
|
||||
|
||||
public function testExpensePut()
|
||||
{
|
||||
$data = [
|
||||
'public_notes' => $this->faker->firstName,
|
||||
'id_number' => 'Coolio',
|
||||
'number' => 'Coolio',
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
@ -70,6 +93,22 @@ class ExpenseApiTest extends TestCase
|
||||
])->put('/api/v1/expenses/'.$this->encodePrimaryKey($this->expense->id), $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/expenses/'.$this->encodePrimaryKey($this->expense->id), $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/expenses/', $data);
|
||||
|
||||
$response->assertStatus(302);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function testExpenseGet()
|
||||
|
@ -125,8 +125,15 @@ class InvoiceTest extends TestCase
|
||||
])->post('/api/v1/invoices/', $invoice)
|
||||
->assertStatus(200);
|
||||
|
||||
//test that the same request should produce a validation error due
|
||||
//to duplicate number being used.
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/invoices/'.$arr['data']['id'], $invoice)
|
||||
->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
|
@ -104,6 +104,15 @@ class PaymentTest extends TestCase
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/payments/'.$this->encodePrimaryKey($Payment->id), $Payment->toArray());
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
|
@ -16,6 +16,7 @@ use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
/**
|
||||
* @test
|
||||
@ -56,6 +57,7 @@ class ProjectApiTest extends TestCase
|
||||
$data = [
|
||||
'name' => $this->faker->firstName,
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'number' => 'duplicate',
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
@ -64,6 +66,26 @@ class ProjectApiTest extends TestCase
|
||||
])->post('/api/v1/projects', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/projects/'.$arr['data']['id'], $data)->assertStatus(200);
|
||||
|
||||
try{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/projects', $data);
|
||||
}
|
||||
catch(ValidationException $e){
|
||||
$response->assertStatus(302);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function testProjectPut()
|
||||
|
@ -86,7 +86,8 @@ class QuoteTest extends TestCase
|
||||
|
||||
$quote_update = [
|
||||
'status_id' => Quote::STATUS_APPROVED,
|
||||
// 'client_id' => $this->encodePrimaryKey($quote->client_id),
|
||||
'client_id' => $this->encodePrimaryKey($this->quote->client_id),
|
||||
'number' => 'Rando',
|
||||
];
|
||||
|
||||
$this->assertNotNull($this->quote);
|
||||
@ -98,6 +99,22 @@ class QuoteTest extends TestCase
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/quotes/'.$this->encodePrimaryKey($this->quote->id), $quote_update);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/quotes/', $quote_update);
|
||||
|
||||
$response->assertStatus(302);
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
|
@ -117,6 +117,7 @@ class RecurringInvoiceTest extends TestCase
|
||||
$RecurringInvoice_update = [
|
||||
'status_id' => RecurringInvoice::STATUS_DRAFT,
|
||||
'client_id' => $this->encodePrimaryKey($RecurringInvoice->client_id),
|
||||
'number' => 'customnumber'
|
||||
];
|
||||
|
||||
$this->assertNotNull($RecurringInvoice);
|
||||
@ -127,6 +128,26 @@ class RecurringInvoiceTest extends TestCase
|
||||
])->put('/api/v1/recurring_invoices/'.$this->encodePrimaryKey($RecurringInvoice->id), $RecurringInvoice_update)
|
||||
->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals('customnumber', $arr['data']['number']);
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/recurring_invoices/'.$this->encodePrimaryKey($RecurringInvoice->id), $RecurringInvoice_update)
|
||||
->assertStatus(200);
|
||||
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/recurring_invoices/', $RecurringInvoice_update)
|
||||
->assertStatus(302);
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
|
@ -14,6 +14,7 @@ use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
@ -44,6 +45,7 @@ class TaskApiTest extends TestCase
|
||||
{
|
||||
$data = [
|
||||
'description' => $this->faker->firstName,
|
||||
'number' => 'taskynumber'
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
@ -54,6 +56,27 @@ class TaskApiTest extends TestCase
|
||||
$arr = $response->json();
|
||||
$response->assertStatus(200);
|
||||
|
||||
$this->assertEquals('taskynumber', $arr['data']['number']);
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/tasks/'.$arr['data']['id'], $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
try{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/tasks', $data);
|
||||
|
||||
$arr = $response->json();
|
||||
}catch(ValidationException $e){
|
||||
$response->assertStatus(302);
|
||||
}
|
||||
|
||||
$this->assertNotEmpty($arr['data']['number']);
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
@ -59,6 +60,7 @@ class VendorApiTest extends TestCase
|
||||
$data = [
|
||||
'name' => $this->faker->firstName,
|
||||
'id_number' => 'Coolio',
|
||||
'number' => 'wiggles'
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
@ -67,6 +69,33 @@ class VendorApiTest extends TestCase
|
||||
])->put('/api/v1/vendors/'.$this->encodePrimaryKey($this->vendor->id), $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals('Coolio', $arr['data']['id_number']);
|
||||
$this->assertEquals('wiggles', $arr['data']['number']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/vendors/'.$this->encodePrimaryKey($this->vendor->id), $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
|
||||
try{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/vendors/', $data);
|
||||
|
||||
|
||||
}catch(ValidationException $e){
|
||||
|
||||
$response->assertStatus(302);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function testVendorGet()
|
||||
|
Loading…
x
Reference in New Issue
Block a user