Merge pull request #6996 from turbo124/v5-stable

v5.3.33
This commit is contained in:
David Bomba 2021-11-25 17:35:20 +11:00 committed by GitHub
commit dcc4b13524
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
89 changed files with 953133 additions and 953619 deletions

View File

@ -1 +1 @@
5.3.32 5.3.33

View File

@ -3,6 +3,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\Company; use App\Models\Company;
use App\Utils\Ninja;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -42,6 +43,10 @@ class S3Cleanup extends Command
public function handle() public function handle()
{ {
if(!Ninja::isHosted())
return;
$c1 = Company::on('db-ninja-01')->pluck('company_key'); $c1 = Company::on('db-ninja-01')->pluck('company_key');
$c2 = Company::on('db-ninja-02')->pluck('company_key'); $c2 = Company::on('db-ninja-02')->pluck('company_key');

View File

@ -52,8 +52,6 @@ class Kernel extends ConsoleKernel
$schedule->job(new DiskCleanup)->daily()->withoutOverlapping(); $schedule->job(new DiskCleanup)->daily()->withoutOverlapping();
$schedule->command('ninja:check-data --database=db-ninja-01')->daily()->withoutOverlapping();
$schedule->job(new ReminderJob)->hourly()->withoutOverlapping(); $schedule->job(new ReminderJob)->hourly()->withoutOverlapping();
$schedule->job(new CompanySizeCheck)->daily()->withoutOverlapping(); $schedule->job(new CompanySizeCheck)->daily()->withoutOverlapping();
@ -84,7 +82,8 @@ class Kernel extends ConsoleKernel
$schedule->job(new AdjustEmailQuota)->dailyAt('23:00')->withoutOverlapping(); $schedule->job(new AdjustEmailQuota)->dailyAt('23:00')->withoutOverlapping();
$schedule->job(new SendFailedEmails)->daily()->withoutOverlapping(); $schedule->job(new SendFailedEmails)->daily()->withoutOverlapping();
$schedule->command('ninja:check-data --database=db-ninja-02')->dailyAt('00:15')->withoutOverlapping(); $schedule->command('ninja:check-data --database=db-ninja-01')->daily()->withoutOverlapping();
$schedule->command('ninja:check-data --database=db-ninja-02')->dailyAt('00:05')->withoutOverlapping();
$schedule->command('ninja:s3-cleanup')->dailyAt('23:15')->withoutOverlapping(); $schedule->command('ninja:s3-cleanup')->dailyAt('23:15')->withoutOverlapping();
} }

View File

@ -21,7 +21,7 @@ trait WithTypeHelpers
*/ */
public function isImage(): bool public function isImage(): bool
{ {
if (in_array($this->type, ['png', 'svg', 'jpeg', 'jpg', 'tiff', 'gif'])) { if (in_array($this->type, ['png', 'jpeg', 'jpg', 'tiff', 'gif'])) {
return true; return true;
} }

View File

@ -346,7 +346,8 @@ class BaseController extends Controller
}, },
'company.subscriptions'=> function ($query) use($updated_at, $user) { 'company.subscriptions'=> function ($query) use($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at); // $query->where('updated_at', '>=', $updated_at);
$query->whereNotNull('updated_at');
if(!$user->isAdmin()) if(!$user->isAdmin())
$query->where('subscriptions.user_id', $user->id); $query->where('subscriptions.user_id', $user->id);

View File

@ -18,14 +18,20 @@ use App\Libraries\MultiDB;
use App\Models\Account; use App\Models\Account;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Models\Company; use App\Models\Company;
use App\Models\Invoice;
use App\Models\RecurringInvoice;
use App\Models\Subscription;
use App\Utils\Ninja; use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\Routing\ResponseFactory; use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
class NinjaPlanController extends Controller class NinjaPlanController extends Controller
{ {
use MakesHash;
public function index(string $contact_key, string $account_or_company_key) public function index(string $contact_key, string $account_or_company_key)
{ {
@ -57,4 +63,82 @@ class NinjaPlanController extends Controller
return redirect()->route('client.catchall'); return redirect()->route('client.catchall');
} }
public function plan()
{
//harvest the current plan
$data = [];
if(MultiDB::findAndSetDbByAccountKey(Auth::guard('contact')->user()->client->custom_value2))
{
$account = Account::where('key', Auth::guard('contact')->user()->client->custom_value2)->first();
if($account)
{
if(Carbon::parse($account->plan_expires)->lt(now())){
//expired get the most recent invoice for payment
$late_invoice = Invoice::on('db-ninja-01')
->where('company_id', Auth::guard('contact')->user()->company->id)
->where('client_id', Auth::guard('contact')->user()->client->id)
->where('status_id', Invoice::STATUS_SENT)
->whereNotNull('subscription_id')
->orderBy('id', 'DESC')
->first();
//account status means user cannot perform upgrades until they pay their account.
$data['late_invoice'] = $late_invoice;
}
$recurring_invoice = RecurringInvoice::on('db-ninja-01')
->where('client_id', auth('contact')->user()->client->id)
->where('company_id', Auth::guard('contact')->user()->company->id)
->whereNotNull('subscription_id')
->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->orderBy('id', 'desc')
->first();
$monthly_plans = Subscription::on('db-ninja-01')
->where('company_id', Auth::guard('contact')->user()->company->id)
->where('group_id', 6)
->orderBy('promo_price', 'ASC')
->get();
$yearly_plans = Subscription::on('db-ninja-01')
->where('company_id', Auth::guard('contact')->user()->company->id)
->where('group_id', 31)
->orderBy('promo_price', 'ASC')
->get();
$monthly_plans = $monthly_plans->merge($yearly_plans);
$current_subscription_id = $recurring_invoice ? $this->encodePrimaryKey($recurring_invoice->subscription_id) : false;
//remove existing subscription
if($current_subscription_id){
$monthly_plans = $monthly_plans->filter(function ($plan) use($current_subscription_id){
return (string)$plan->hashed_id != (string)$current_subscription_id;
});
}
$data['account'] = $account;
$data['client'] = Auth::guard('contact')->user()->client;
$data['plans'] = $monthly_plans;
$data['current_subscription_id'] = $current_subscription_id;
$data['current_recurring_id'] = $recurring_invoice ? $recurring_invoice->hashed_id : false;
return $this->render('plan.index', $data);
}
}
else
return redirect()->route('client.catchall');
}
} }

View File

@ -524,6 +524,11 @@ class CreditController extends BaseController
{ {
/*If we are using bulk actions, we don't want to return anything */ /*If we are using bulk actions, we don't want to return anything */
switch ($action) { switch ($action) {
case 'mark_paid':
$credit->service()->markPaid()->save();
return $this->itemResponse($credit);
break;
case 'clone_to_credit': case 'clone_to_credit':
$credit = CloneCreditFactory::create($credit, auth()->user()->id); $credit = CloneCreditFactory::create($credit, auth()->user()->id);

View File

@ -0,0 +1,67 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Http\Response;
class FilterController extends BaseController
{
private array $base_filters = ['archive', 'restore', 'delete'];
public function __construct()
{
}
/**
* Display a listing of the resource.
*
* @return void
*/
public function index(Request $request, string $entity)
{
$entity_filters = [];
switch ($entity) {
case 'invoice':
$entity_filters = ['bulk_download', 'mark_paid', 'mark_sent', 'download', 'cancel', 'email'];
break;
case 'quote':
$entity_filters = ['bulk_download', 'convert', 'convert_to_invoice', 'download', 'approve', 'email', 'mark_sent'];
break;
case 'credit':
$entity_filters = ['bulk_download', 'download', 'email', 'mark_sent'];
break;
case 'payment':
$entity_filters = ['bulk_download', 'download', 'email', 'email_receipt'];
break;
case 'recurring_invoice':
$entity_filters = ['bulk_download', 'start', 'stop', 'email'];
break;
}
return response()->json( array_merge($this->base_filters, $entity_filters), 200);
}
}

View File

@ -63,29 +63,12 @@ class HostedMigrationController extends Controller
MultiDB::findAndSetDbByCompanyKey($input['account_key']); MultiDB::findAndSetDbByCompanyKey($input['account_key']);
$company = Company::with('account')->where('company_key', $input['account_key'])->first(); $company = Company::with('account')->where('company_key', $input['account_key'])->first();
$account = $company->account;
$client_id = false;
if($contact = ClientContact::on('db-ninja-01')->where(['email' => $input['email'], 'company_id' => config('ninja.ninja_default_company_id')])->first()){
$client_id = $contact->client_id;
}
else if($client = Client::on('db-ninja-01')->where(['custom_value2' => $account->key, 'company_id' => config('ninja.ninja_default_company_id')])->first()){
$client_id = $client->id;
}
//get ninja client_id;
if(strlen($input['gateway_reference']) >1 && $client_id){
Artisan::call('ninja:add-token', [
'--customer' => $input['gateway_reference'], '--client_id' => 1
]);
}
$forward_url = $company->domain(); $forward_url = $company->domain();
return response()->json(['forward_url' => $forward_url], 200); $billing_transferred = \Modules\Admin\Jobs\Account\TransferAccountPlan::dispatchNow($input);
return response()->json(['forward_url' => $forward_url, 'billing_transferred' => $billing_transferred], 200);
} }
} }

View File

@ -345,14 +345,6 @@ class BillingPortalPurchase extends Component
'quantity' => $this->quantity, 'quantity' => $this->quantity,
]; ];
$this->invoice = $this->subscription
->service()
->createInvoice($data)
->service()
->markSent()
->fillDefaults()
->save();
$is_eligible = $this->subscription->service()->isEligible($this->contact); $is_eligible = $this->subscription->service()->isEligible($this->contact);
if (is_array($is_eligible) && $is_eligible['message'] != 'Success') { if (is_array($is_eligible) && $is_eligible['message'] != 'Success') {
@ -363,6 +355,14 @@ class BillingPortalPurchase extends Component
return; return;
} }
$this->invoice = $this->subscription
->service()
->createInvoice($data)
->service()
->markSent()
->fillDefaults()
->save();
Cache::put($this->hash, [ Cache::put($this->hash, [
'subscription_id' => $this->subscription->id, 'subscription_id' => $this->subscription->id,
'email' => $this->email ?? $this->contact->email, 'email' => $this->email ?? $this->contact->email,

View File

@ -60,8 +60,8 @@ class RequiredClientInfo extends Component
'contact_first_name' => 'first_name', 'contact_first_name' => 'first_name',
'contact_last_name' => 'last_name', 'contact_last_name' => 'last_name',
'contact_email' => 'email', // 'contact_email' => 'email',
'contact_phone' => 'phone', // 'contact_phone' => 'phone',
]; ];
public $show_form = false; public $show_form = false;
@ -141,7 +141,7 @@ class RequiredClientInfo extends Component
$_field = $this->mappings[$field['name']]; $_field = $this->mappings[$field['name']];
if (Str::startsWith($field['name'], 'client_')) { if (Str::startsWith($field['name'], 'client_')) {
if (empty($this->contact->client->{$_field}) || is_null($this->contact->client->{$_field}) || $this->contact->client->{$_field} = 840) { if (empty($this->contact->client->{$_field}) || is_null($this->contact->client->{$_field}) || $this->contact->client->{$_field} == 840) {
$this->show_form = true; $this->show_form = true;
} else { } else {
$this->fields[$index]['filled'] = true; $this->fields[$index]['filled'] = true;
@ -149,7 +149,7 @@ class RequiredClientInfo extends Component
} }
if (Str::startsWith($field['name'], 'contact_')) { if (Str::startsWith($field['name'], 'contact_')) {
if ((empty($this->contact->{$_field}) || is_null($this->contact->{$_field})) || $this->contact->client->{$_field} = 840) { if ((empty($this->contact->{$_field}) || is_null($this->contact->{$_field})) || $this->contact->client->{$_field} == 840) {
$this->show_form = true; $this->show_form = true;
} else { } else {
$this->fields[$index]['filled'] = true; $this->fields[$index]['filled'] = true;

View File

@ -38,6 +38,7 @@ class SubscriptionRecurringInvoicesTable extends Component
->where('client_id', auth('contact')->user()->client->id) ->where('client_id', auth('contact')->user()->client->id)
->where('company_id', $this->company->id) ->where('company_id', $this->company->id)
->whereNotNull('subscription_id') ->whereNotNull('subscription_id')
->where('is_deleted', false)
->where('status_id', RecurringInvoice::STATUS_ACTIVE) ->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc') ->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
->withTrashed() ->withTrashed()

View File

@ -32,7 +32,7 @@ class ContactAccount
if(!Ninja::isHosted()) { if(!Ninja::isHosted()) {
$account_id = Account::first()->id; $account_id = Account::first()->id;
$request->attributes->add(['account_id' => $account_id]); $request->request->add(['account_id' => $account_id]);
} }

View File

@ -49,7 +49,7 @@ class SetDomainNameDb
]; ];
if($company = MultiDB::findAndSetDbByDomain($query)){ if($company = MultiDB::findAndSetDbByDomain($query)){
$request->attributes->add(['account_id' => $company->account_id]); $request->request->add(['account_id' => $company->account_id]);
} }
else else
{ {
@ -71,7 +71,7 @@ class SetDomainNameDb
]; ];
if($company = MultiDB::findAndSetDbByDomain($query)){ if($company = MultiDB::findAndSetDbByDomain($query)){
$request->attributes->add(['account_id' => $company->account_id]); $request->request->add(['account_id' => $company->account_id]);
} }
else else
{ {

View File

@ -42,10 +42,10 @@ class StoreClientRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
if (isset($this->number)) { if (isset($this->number)) {

View File

@ -41,10 +41,10 @@ class UpdateClientRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
$rules['company_logo'] = 'mimes:jpeg,jpg,png,gif|max:10000'; $rules['company_logo'] = 'mimes:jpeg,jpg,png,gif|max:10000';

View File

@ -31,7 +31,7 @@ class UploadClientRequest extends Request
$rules = []; $rules = [];
if($this->input('documents')) if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules; return $rules;

View File

@ -29,7 +29,7 @@ class StoreDocumentRequest extends Request
public function rules() public function rules()
{ {
return [ return [
'file' => 'required|max:10000|mimes:png,svg,jpeg,gif,jpg,bmp,txt,doc,docx,xls,xlsx,pdf', 'file' => 'required|max:10000|mimes:png,jpeg,gif,jpg,bmp,txt,doc,docx,xls,xlsx,pdf',
]; ];
} }

View File

@ -32,7 +32,7 @@ class UpdateClientRequest extends Request
{ {
return [ return [
'name' => 'sometimes|required', 'name' => 'sometimes|required',
'file' => 'sometimes|nullable|max:100000|mimes:png,svg,jpeg,gif,jpg,bmp', 'file' => 'sometimes|nullable|max:100000|mimes:png,jpeg,gif,jpg,bmp',
]; ];
} }
} }

View File

@ -24,7 +24,7 @@ class StoreUploadRequest extends FormRequest
public function rules() public function rules()
{ {
return [ return [
'file' => ['file', 'mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'], 'file' => ['file', 'mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'],
]; ];
} }

View File

@ -31,7 +31,7 @@ class UploadCompanyRequest extends Request
$rules = []; $rules = [];
if($this->input('documents')) if($this->input('documents'))
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
return $rules; return $rules;

View File

@ -46,16 +46,17 @@ class StoreCreditRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
$rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id; $rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id;
// $rules['number'] = new UniqueCreditNumberRule($this->all()); // $rules['number'] = new UniqueCreditNumberRule($this->all());
$rules['number'] = ['nullable', Rule::unique('credits')->where('company_id', auth()->user()->company()->id)]; $rules['number'] = ['nullable', Rule::unique('credits')->where('company_id', auth()->user()->company()->id)];
$rules['discount'] = 'sometimes|numeric';

View File

@ -47,16 +47,17 @@ class UpdateCreditRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
if($this->number) if($this->number)
$rules['number'] = Rule::unique('credits')->where('company_id', auth()->user()->company()->id)->ignore($this->credit->id); $rules['number'] = Rule::unique('credits')->where('company_id', auth()->user()->company()->id)->ignore($this->credit->id);
$rules['line_items'] = 'array'; $rules['line_items'] = 'array';
$rules['discount'] = 'sometimes|numeric';
return $rules; return $rules;
} }

View File

@ -31,7 +31,7 @@ class UploadCreditRequest extends Request
$rules = []; $rules = [];
if($this->input('documents')) if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules; return $rules;

View File

@ -31,7 +31,7 @@ class UploadExpenseRequest extends Request
$rules = []; $rules = [];
if($this->input('documents')) if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules; return $rules;

View File

@ -31,7 +31,7 @@ class UploadGroupSettingRequest extends Request
$rules = []; $rules = [];
if($this->input('documents')) if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules; return $rules;

View File

@ -41,10 +41,10 @@ class StoreInvoiceRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
$rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id; $rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id;
@ -56,6 +56,7 @@ class StoreInvoiceRequest extends Request
$rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())]; $rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())];
$rules['line_items'] = 'array'; $rules['line_items'] = 'array';
$rules['discount'] = 'sometimes|numeric';
return $rules; return $rules;
} }

View File

@ -44,10 +44,10 @@ class UpdateInvoiceRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
$rules['id'] = new LockedInvoiceRule($this->invoice); $rules['id'] = new LockedInvoiceRule($this->invoice);
@ -56,6 +56,7 @@ class UpdateInvoiceRequest extends Request
$rules['number'] = Rule::unique('invoices')->where('company_id', auth()->user()->company()->id)->ignore($this->invoice->id); $rules['number'] = Rule::unique('invoices')->where('company_id', auth()->user()->company()->id)->ignore($this->invoice->id);
$rules['line_items'] = 'array'; $rules['line_items'] = 'array';
$rules['discount'] = 'sometimes|numeric';
if($this->input('status_id') != Invoice::STATUS_DRAFT) if($this->input('status_id') != Invoice::STATUS_DRAFT)
$rules['balance'] = new InvoiceBalanceSanity($this->invoice, $this->all()); $rules['balance'] = new InvoiceBalanceSanity($this->invoice, $this->all());

View File

@ -31,7 +31,7 @@ class UploadInvoiceRequest extends Request
$rules = []; $rules = [];
if($this->input('documents')) if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules; return $rules;

View File

@ -107,10 +107,10 @@ class StorePaymentRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
return $rules; return $rules;

View File

@ -38,7 +38,7 @@ class UpdatePaymentRequest extends Request
$rules = [ $rules = [
'invoices' => ['array', new PaymentAppliedValidAmount, new ValidCreditsPresentRule], 'invoices' => ['array', new PaymentAppliedValidAmount, new ValidCreditsPresentRule],
'invoices.*.invoice_id' => 'distinct', 'invoices.*.invoice_id' => 'distinct',
'documents' => 'mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx', 'documents' => 'mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx',
]; ];
if($this->number) if($this->number)
@ -48,10 +48,10 @@ class UpdatePaymentRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
return $rules; return $rules;

View File

@ -31,7 +31,7 @@ class UploadPaymentRequest extends Request
$rules = []; $rules = [];
if($this->input('documents')) if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules; return $rules;

View File

@ -32,10 +32,10 @@ class StoreProductRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
$rules['cost'] = 'numeric'; $rules['cost'] = 'numeric';

View File

@ -35,10 +35,10 @@ class UpdateProductRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
$rules['cost'] = 'numeric'; $rules['cost'] = 'numeric';

View File

@ -31,7 +31,7 @@ class UploadProductRequest extends Request
$rules = []; $rules = [];
if($this->input('documents')) if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules; return $rules;

View File

@ -31,7 +31,7 @@ class UploadProjectRequest extends Request
$rules = []; $rules = [];
if($this->input('documents')) if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules; return $rules;

View File

@ -43,13 +43,14 @@ class StoreQuoteRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
$rules['number'] = ['nullable',Rule::unique('quotes')->where('company_id', auth()->user()->company()->id)]; $rules['number'] = ['nullable',Rule::unique('quotes')->where('company_id', auth()->user()->company()->id)];
$rules['discount'] = 'sometimes|numeric';
// $rules['number'] = new UniqueQuoteNumberRule($this->all()); // $rules['number'] = new UniqueQuoteNumberRule($this->all());
$rules['line_items'] = 'array'; $rules['line_items'] = 'array';

View File

@ -41,16 +41,17 @@ class UpdateQuoteRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
if($this->number) if($this->number)
$rules['number'] = Rule::unique('quotes')->where('company_id', auth()->user()->company()->id)->ignore($this->quote->id); $rules['number'] = Rule::unique('quotes')->where('company_id', auth()->user()->company()->id)->ignore($this->quote->id);
$rules['line_items'] = 'array'; $rules['line_items'] = 'array';
$rules['discount'] = 'sometimes|numeric';
return $rules; return $rules;
} }

View File

@ -31,7 +31,7 @@ class UploadQuoteRequest extends Request
$rules = []; $rules = [];
if($this->input('documents')) if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules; return $rules;

View File

@ -31,7 +31,7 @@ class UploadRecurringExpenseRequest extends Request
$rules = []; $rules = [];
if($this->input('documents')) if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules; return $rules;

View File

@ -42,11 +42,11 @@ class StoreRecurringInvoiceRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
$rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id; $rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id;

View File

@ -42,10 +42,10 @@ class UpdateRecurringInvoiceRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
if($this->number) if($this->number)

View File

@ -31,7 +31,7 @@ class UploadRecurringInvoiceRequest extends Request
$rules = []; $rules = [];
if($this->input('documents')) if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules; return $rules;

View File

@ -42,11 +42,11 @@ class StoreRecurringQuoteRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
$rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id; $rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id;

View File

@ -42,10 +42,10 @@ class UpdateRecurringQuoteRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
if($this->number) if($this->number)

View File

@ -31,7 +31,7 @@ class UploadRecurringQuoteRequest extends Request
$rules = []; $rules = [];
if($this->input('documents')) if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules; return $rules;

View File

@ -43,10 +43,10 @@ class StoreShopClientRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
/* Ensure we have a client name, and that all emails are unique*/ /* Ensure we have a client name, and that all emails are unique*/

View File

@ -42,10 +42,10 @@ class StoreShopInvoiceRequest extends Request
$documents = count($this->input('documents')); $documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) { foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
} elseif ($this->input('documents')) { } elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
$rules['client_id'] = 'required|exists:clients,id,company_id,'.$this->company->id; $rules['client_id'] = 'required|exists:clients,id,company_id,'.$this->company->id;

View File

@ -31,7 +31,7 @@ class UploadTaskRequest extends Request
$rules = []; $rules = [];
if($this->input('documents')) if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules; return $rules;

View File

@ -31,7 +31,7 @@ class UploadVendorRequest extends Request
$rules = []; $rules = [];
if($this->input('documents')) if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules; return $rules;

View File

@ -121,6 +121,10 @@ class PortalComposer
$data[] = ['title' => ctrans('texts.statement'), 'url' => 'client.statement', 'icon' => 'activity']; $data[] = ['title' => ctrans('texts.statement'), 'url' => 'client.statement', 'icon' => 'activity'];
if(Ninja::isHosted() && auth('contact')->user()->company->id == config('ninja.ninja_default_company_id'))
$data[] = ['title' => ctrans('texts.plan'), 'url' => 'client.plan', 'icon' => 'credit-card'];
return $data; return $data;
} }
} }

View File

@ -188,9 +188,9 @@ class CreateRawPdf implements ShouldQueue
nlog(print_r($e->getMessage(), 1)); nlog(print_r($e->getMessage(), 1));
} }
if (config('ninja.log_pdf_html')) { // if (config('ninja.log_pdf_html')) {
info($maker->getCompiledHTML()); info($maker->getCompiledHTML());
} // }
if ($pdf) if ($pdf)
return $pdf; return $pdf;

View File

@ -42,8 +42,5 @@ class DiskCleanup implements ShouldQueue
$files = Storage::allFiles(config('filesystems.default'), 'backups/'); $files = Storage::allFiles(config('filesystems.default'), 'backups/');
Storage::delete($files); Storage::delete($files);
$files = Storage::allFiles('public', 'backups/');
Storage::delete($files);
} }
} }

View File

@ -42,9 +42,6 @@ class Document extends BaseModel
'ai' => [ 'ai' => [
'mime' => 'application/postscript', 'mime' => 'application/postscript',
], ],
'svg' => [
'mime' => 'image/svg+xml',
],
'jpeg' => [ 'jpeg' => [
'mime' => 'image/jpeg', 'mime' => 'image/jpeg',
], ],

View File

@ -136,7 +136,10 @@ class CreditCard
return $this->processSuccessfulPayment($result); return $this->processSuccessfulPayment($result);
} }
return $this->processUnsuccessfulPayment($result); $error = $result ?: 'Undefined gateway error';
return $this->processUnsuccessfulPayment($error);
} }
private function getPaymentToken(array $data, $customerId): ?string private function getPaymentToken(array $data, $customerId): ?string

View File

@ -16,6 +16,7 @@ use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Http\Requests\Gateways\Mollie\Mollie3dsRequest; use App\Http\Requests\Gateways\Mollie\Mollie3dsRequest;
use App\Http\Requests\Payments\PaymentWebhookRequest; use App\Http\Requests\Payments\PaymentWebhookRequest;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
use App\Models\Client;
use App\Models\ClientGatewayToken; use App\Models\ClientGatewayToken;
use App\Models\GatewayType; use App\Models\GatewayType;
use App\Models\Invoice; use App\Models\Invoice;
@ -175,7 +176,7 @@ class MolliePaymentDriver extends BaseDriver
SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_MOLLIE, SystemLog::TYPE_MOLLIE,
$this->client, $this->client,
$this->client->companyk $this->client->company
); );
nlog($e->getMessage()); nlog($e->getMessage());
@ -238,7 +239,8 @@ class MolliePaymentDriver extends BaseDriver
SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_MOLLIE, SystemLog::TYPE_MOLLIE,
$this->client $this->client,
$this->client->company
); );
return $payment; return $payment;
@ -258,7 +260,8 @@ class MolliePaymentDriver extends BaseDriver
SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_CHECKOUT, SystemLog::TYPE_CHECKOUT,
$this->client $this->client,
$this->client->company
); );
return false; return false;
@ -303,12 +306,42 @@ class MolliePaymentDriver extends BaseDriver
try { try {
$payment = $this->gateway->payments->get($request->id); $payment = $this->gateway->payments->get($request->id);
$record = Payment::withTrashed()->where('transaction_reference', $request->id)->first();
$record = Payment::withTrashed()->where('transaction_reference', $request->id)->firstOrFail(); if($record){
$record->status_id = $codes[$payment->status]; $client = $record->client;
$record->save(); }
else{
nlog("mollie webhook");
nlog($payment);
$client = Client::withTrashed()->find($this->decodePrimaryKey($payment->metadata->client_id));
}
$message = [
'server_response' => $payment,
'data' => $request->all(),
];
$response = SystemLog::EVENT_GATEWAY_FAILURE;
if($record){
$record->status_id = $codes[$payment->status];
$record->save();
$response = SystemLog::EVENT_GATEWAY_SUCCESS;
}
SystemLogger::dispatch(
$message,
SystemLog::CATEGORY_GATEWAY_RESPONSE,
$response,
SystemLog::TYPE_MOLLIE,
$client,
$client->company
);
return response()->json([], 200); return response()->json([], 200);
} catch (ApiException $e) { } catch (ApiException $e) {
return response()->json(['message' => $e->getMessage(), 'gatewayStatusCode' => $e->getCode()], 500); return response()->json(['message' => $e->getMessage(), 'gatewayStatusCode' => $e->getCode()], 500);
} }

View File

@ -231,12 +231,24 @@ class ACH
$this->stripe->payment_hash->data = array_merge((array)$this->stripe->payment_hash->data, $state); $this->stripe->payment_hash->data = array_merge((array)$this->stripe->payment_hash->data, $state);
$this->stripe->payment_hash->save(); $this->stripe->payment_hash->save();
$amount = array_sum(array_column($this->stripe->payment_hash->invoices(), 'amount')) + $this->stripe->payment_hash->fee_total;
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($this->stripe->payment_hash->invoices(), 'invoice_id')))
->withTrashed()
->first();
if ($invoice) {
$description = "Invoice {$invoice->number} for {$amount} for client {$this->stripe->client->present()->name()}";
} else {
$description = "Payment with no invoice for amount {$amount} for client {$this->stripe->client->present()->name()}";
}
try { try {
$state['charge'] = \Stripe\Charge::create([ $state['charge'] = \Stripe\Charge::create([
'amount' => $state['amount'], 'amount' => $state['amount'],
'currency' => $state['currency'], 'currency' => $state['currency'],
'customer' => $state['customer'], 'customer' => $state['customer'],
'source' => $state['source'], 'source' => $state['source'],
'description' => $description,
], $this->stripe->stripe_connect_auth); ], $this->stripe->stripe_connect_auth);
$state = array_merge($state, $request->all()); $state = array_merge($state, $request->all());

View File

@ -208,7 +208,7 @@ class WePayPaymentDriver extends BaseDriver
return 'Processed successfully'; return 'Processed successfully';
} elseif ($objectType == 'account') { } elseif ($objectType == 'account') {
if ($accountId !== $objectId) { if ($accountId != $objectId) {
throw new \Exception('Unknown account ' . $accountId . ' does not equal '.$objectId); throw new \Exception('Unknown account ' . $accountId . ' does not equal '.$objectId);
} }

View File

@ -176,7 +176,7 @@ class BaseRepository
if(array_key_exists('client_id', $data)) if(array_key_exists('client_id', $data))
$model->client_id = $data['client_id']; $model->client_id = $data['client_id'];
$client = Client::where('id', $model->client_id)->withTrashed()->first(); $client = Client::where('id', $model->client_id)->withTrashed()->firstOrFail();
$state = []; $state = [];

View File

@ -185,13 +185,13 @@ class PaymentRepository extends BaseRepository {
* @param $payment * @param $payment
* @return * @return
*/ */
private function processExchangeRates($data, $payment) public function processExchangeRates($data, $payment)
{ {
if(array_key_exists('exchange_rate', $data) && isset($data['exchange_rate'])) if(array_key_exists('exchange_rate', $data) && isset($data['exchange_rate']))
return $payment; return $payment;
$client = Client::find($data['client_id']); $client = Client::withTrashed()->find($data['client_id']);
$client_currency = $client->getSetting('currency_id'); $client_currency = $client->getSetting('currency_id');
$company_currency = $client->company->settings->currency_id; $company_currency = $client->company->settings->currency_id;

View File

@ -11,8 +11,13 @@
namespace App\Services\Credit; namespace App\Services\Credit;
use App\Factory\PaymentFactory;
use App\Jobs\Util\UnlinkFile; use App\Jobs\Util\UnlinkFile;
use App\Models\Credit; use App\Models\Credit;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Repositories\CreditRepository;
use App\Repositories\PaymentRepository;
use App\Services\Credit\CreateInvitations; use App\Services\Credit\CreateInvitations;
use App\Services\Credit\TriggeredActions; use App\Services\Credit\TriggeredActions;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
@ -79,6 +84,61 @@ class CreditService
return $this; return $this;
} }
/*
For euro users - we mark a credit as paid when
we need to document a refund of sorts.
Criteria: Credit must be a negative value
A negative payment for the balance will be generated
This amount will be reduced from the clients paid to date.
*/
public function markPaid()
{
if($this->credit->balance > 0)
return $this;
$payment_repo = new PaymentRepository(new CreditRepository());
//set credit balance to zero
$adjustment = $this->credit->balance;
$this->updateBalance($adjustment)
->updatePaidToDate($adjustment)
->save();
//create a negative payment of total $this->credit->balance
$payment = PaymentFactory::create($this->credit->company_id, $this->credit->user_id);
$payment->client_id = $this->credit->client_id;
$payment->amount = $adjustment;
$payment->applied = $adjustment;
$payment->refunded = 0;
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->type_id = PaymentType::CREDIT;
$payment->is_manual = true;
$payment->date = now();
$payment->saveQuietly();
$payment->number = $payment->client->getNextPaymentNumber($payment->client, $payment);
$payment = $payment_repo->processExchangeRates(['client_id' => $this->credit->client_id], $payment);
$payment->saveQuietly();
$payment
->credits()
->attach($this->credit->id, ['amount' => $adjustment]);
//reduce client paid_to_date by $this->credit->balance amount
$this->credit
->client
->service()
->updatePaidToDate($adjustment)
->save();
event('eloquent.created: App\Models\Payment', $payment);
return $this;
}
public function markSent() public function markSent()
{ {
$this->credit = (new MarkSent($this->credit->client, $this->credit))->run(); $this->credit = (new MarkSent($this->credit->client, $this->credit))->run();

View File

@ -130,7 +130,7 @@ class AutoBillInvoice extends AbstractService
info("Auto Bill payment captured for ".$this->invoice->number); info("Auto Bill payment captured for ".$this->invoice->number);
} }
return $this->invoice; return $this->invoice->fresh();
} }
/** /**

View File

@ -33,7 +33,7 @@ class CreateInvitations extends AbstractService
public function run() public function run()
{ {
$contacts = $this->invoice->client->contacts; $contacts = $this->invoice->client->contacts()->where('send_email', true)->get();
if($contacts->count() == 0){ if($contacts->count() == 0){
$this->createBlankContact(); $this->createBlankContact();

View File

@ -139,8 +139,13 @@ class DeletePayment
if ($this->payment->credits()->exists()) { if ($this->payment->credits()->exists()) {
$this->payment->credits()->each(function ($paymentable_credit) { $this->payment->credits()->each(function ($paymentable_credit) {
$multiplier = 1;
if($paymentable_credit->pivot->amount < 0)
$multiplier = -1;
$paymentable_credit->service() $paymentable_credit->service()
->updateBalance($paymentable_credit->pivot->amount) ->updateBalance($paymentable_credit->pivot->amount*$multiplier)
->updatePaidToDate($paymentable_credit->pivot->amount*-1) ->updatePaidToDate($paymentable_credit->pivot->amount*-1)
->setStatus(Credit::STATUS_SENT) ->setStatus(Credit::STATUS_SENT)
->save(); ->save();

View File

@ -18,6 +18,7 @@ use App\Factory\InvoiceToRecurringInvoiceFactory;
use App\Factory\RecurringInvoiceFactory; use App\Factory\RecurringInvoiceFactory;
use App\Jobs\Util\SubscriptionWebhookHandler; use App\Jobs\Util\SubscriptionWebhookHandler;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
use App\Libraries\MultiDB;
use App\Models\Client; use App\Models\Client;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Models\Credit; use App\Models\Credit;
@ -240,10 +241,10 @@ class SubscriptionService
elseif ($outstanding->count() > 1) { elseif ($outstanding->count() > 1) {
//user is changing plan mid frequency cycle //user is changing plan mid frequency cycle
//we cannot handle this if there are more than one invoice outstanding. //we cannot handle this if there are more than one invoice outstanding.
return null; return $target->price;
} }
return null; return $target->price;
} }
@ -439,7 +440,7 @@ class SubscriptionService
$credit = false; $credit = false;
/* Only generate a credit if the previous invoice was paid in full. */ /* Only generate a credit if the previous invoice was paid in full. */
if($last_invoice->balance == 0) if($last_invoice && $last_invoice->balance == 0)
$credit = $this->createCredit($last_invoice, $target_subscription, $is_credit); $credit = $this->createCredit($last_invoice, $target_subscription, $is_credit);
$new_recurring_invoice = $this->createNewRecurringInvoice($recurring_invoice); $new_recurring_invoice = $this->createNewRecurringInvoice($recurring_invoice);
@ -717,8 +718,9 @@ class SubscriptionService
*/ */
public function convertInvoiceToRecurring($client_id) :RecurringInvoice public function convertInvoiceToRecurring($client_id) :RecurringInvoice
{ {
MultiDB::setDb($this->subscription->company->db);
$client = Client::find($client_id); $client = Client::withTrashed()->find($client_id);
$subscription_repo = new SubscriptionRepository(); $subscription_repo = new SubscriptionRepository();

View File

@ -60,7 +60,7 @@ class UserTransformer extends EntityTransformer
'oauth_provider_id' => (string) $user->oauth_provider_id, 'oauth_provider_id' => (string) $user->oauth_provider_id,
'last_confirmed_email_address' => (string) $user->last_confirmed_email_address ?: '', 'last_confirmed_email_address' => (string) $user->last_confirmed_email_address ?: '',
'google_2fa_secret' => (bool) $user->google_2fa_secret, 'google_2fa_secret' => (bool) $user->google_2fa_secret,
'has_password' => (bool) $user->has_password, 'has_password' => (bool) empty($user->password) ? false : true,
'oauth_user_token' => empty($user->oauth_user_token) ? '' : '***', 'oauth_user_token' => empty($user->oauth_user_token) ? '' : '***',
]; ];
} }

View File

@ -98,8 +98,7 @@
"laravel/dusk": "^6.15", "laravel/dusk": "^6.15",
"mockery/mockery": "^1.3.1", "mockery/mockery": "^1.3.1",
"nunomaduro/collision": "^5.0", "nunomaduro/collision": "^5.0",
"phpunit/phpunit": "^9.0", "phpunit/phpunit": "^9.0"
"vimeo/psalm": "^4.0"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

1655
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.3.32', 'app_version' => '5.3.33',
'app_tag' => '5.3.32', 'app_tag' => '5.3.33',
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''), 'api_secret' => env('API_SECRET', ''),

10431
package-lock.json generated

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,38 +3,38 @@ const MANIFEST = 'flutter-app-manifest';
const TEMP = 'flutter-temp-cache'; const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache'; const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = { const RESOURCES = {
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35", "manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
"/": "6310be19342ca9ed6920572bcc23151f",
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
"favicon.ico": "51636d3a390451561744c42188ccd628",
"version.json": "9c7b0edc83733da56c726678aacd9fd3", "version.json": "9c7b0edc83733da56c726678aacd9fd3",
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
"favicon.ico": "51636d3a390451561744c42188ccd628",
"main.dart.js": "40a468819694baa11ae02390609712fc",
"/": "38d8084156eb614c31d736ce8c9bd255",
"assets/NOTICES": "5a96be85b952e4fcd3a6965546c85b7f",
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "015400679694f1f51047e46da0e1dc98",
"assets/AssetManifest.json": "38d9aea341601f3a5c6fa7b5a1216ea5",
"assets/fonts/MaterialIcons-Regular.otf": "4e6447691c9509f7acdbf8a931a85ca1", "assets/fonts/MaterialIcons-Regular.otf": "4e6447691c9509f7acdbf8a931a85ca1",
"assets/NOTICES": "7610cf8f301427a1104669ea3f4074ac",
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296",
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f", "assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
"assets/assets/images/payment_types/paypal.png": "8e06c094c1871376dfea1da8088c29d1", "assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219",
"assets/assets/images/payment_types/mastercard.png": "6f6cdc29ee2e22e06b1ac029cb52ef71",
"assets/assets/images/payment_types/carteblanche.png": "d936e11fa3884b8c9f1bd5c914be8629",
"assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
"assets/assets/images/payment_types/other.png": "d936e11fa3884b8c9f1bd5c914be8629",
"assets/assets/images/payment_types/discover.png": "6c0a386a00307f87db7bea366cca35f5",
"assets/assets/images/payment_types/amex.png": "c49a4247984b3732a4af50a3390aa978",
"assets/assets/images/payment_types/ach.png": "7433f0aff779dc98a649b7a2daf777cf",
"assets/assets/images/payment_types/jcb.png": "07e0942d16c5592118b72e74f2f7198c",
"assets/assets/images/payment_types/laser.png": "b4e6e93dd35517ac429301119ff05868",
"assets/assets/images/payment_types/visa.png": "3ddc4a4d25c946e8ad7e6998f30fd4e3",
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
"assets/assets/images/payment_types/maestro.png": "e533b92bfb50339fdbfa79e3dfe81f08",
"assets/assets/images/payment_types/unionpay.png": "7002f52004e0ab8cc0b7450b0208ccb2",
"assets/assets/images/logo_dark.png": "a233ed1d4d0f7414bf97a9a10f11fb0a", "assets/assets/images/logo_dark.png": "a233ed1d4d0f7414bf97a9a10f11fb0a",
"assets/assets/images/icon.png": "090f69e23311a4b6d851b3880ae52541", "assets/assets/images/icon.png": "090f69e23311a4b6d851b3880ae52541",
"assets/assets/images/google_logo.png": "0f118259ce403274f407f5e982e681c3", "assets/assets/images/payment_types/paypal.png": "8e06c094c1871376dfea1da8088c29d1",
"assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219", "assets/assets/images/payment_types/maestro.png": "e533b92bfb50339fdbfa79e3dfe81f08",
"assets/AssetManifest.json": "38d9aea341601f3a5c6fa7b5a1216ea5", "assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
"main.dart.js": "d57b5bf95106fcc642bac2ab5bc52fc3" "assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
"assets/assets/images/payment_types/other.png": "d936e11fa3884b8c9f1bd5c914be8629",
"assets/assets/images/payment_types/unionpay.png": "7002f52004e0ab8cc0b7450b0208ccb2",
"assets/assets/images/payment_types/discover.png": "6c0a386a00307f87db7bea366cca35f5",
"assets/assets/images/payment_types/jcb.png": "07e0942d16c5592118b72e74f2f7198c",
"assets/assets/images/payment_types/carteblanche.png": "d936e11fa3884b8c9f1bd5c914be8629",
"assets/assets/images/payment_types/laser.png": "b4e6e93dd35517ac429301119ff05868",
"assets/assets/images/payment_types/ach.png": "7433f0aff779dc98a649b7a2daf777cf",
"assets/assets/images/payment_types/mastercard.png": "6f6cdc29ee2e22e06b1ac029cb52ef71",
"assets/assets/images/payment_types/amex.png": "c49a4247984b3732a4af50a3390aa978",
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
"assets/assets/images/payment_types/visa.png": "3ddc4a4d25c946e8ad7e6998f30fd4e3",
"assets/assets/images/google_logo.png": "0f118259ce403274f407f5e982e681c3"
}; };
// The application shell files that are downloaded before a service worker can // The application shell files that are downloaded before a service worker can

View File

@ -0,0 +1 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" overflow="visible" preserveAspectRatio="none" viewBox="0 0 35 28" width="35" height="28"><g transform="translate(0, 0)"><g transform="translate(-6.938893903907228e-17, 0) rotate(0)"><path d="M31.5,0h-28c-1.925,0 -3.4825,1.575 -3.4825,3.5l-0.0175,21c0,1.925 1.575,3.5 3.5,3.5h28c1.925,0 3.5,-1.575 3.5,-3.5v-21c0,-1.925 -1.575,-3.5 -3.5,-3.5zM31.5,7l-14,8.75l-14,-8.75v-3.5l14,8.75l14,-8.75z" style="stroke-width: 0; stroke-linecap: butt; stroke-linejoin: miter; fill: rgb(18, 18, 18);" vector-effect="non-scaling-stroke"/></g><defs><path id="path-1637627912051353" d="M31.5,0h-28c-1.925,0 -3.4825,1.575 -3.4825,3.5l-0.0175,21c0,1.925 1.575,3.5 3.5,3.5h28c1.925,0 3.5,-1.575 3.5,-3.5v-21c0,-1.925 -1.575,-3.5 -3.5,-3.5zM31.5,7l-14,8.75l-14,-8.75v-3.5l14,8.75l14,-8.75z" vector-effect="non-scaling-stroke"/></defs></g></svg>

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

385024
public/main.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

381652
public/main.foss.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

380590
public/main.html.dart.js vendored

File diff suppressed because one or more lines are too long

294385
public/main.next.dart.js vendored

File diff suppressed because one or more lines are too long

451126
public/main.profile.dart.js vendored

File diff suppressed because one or more lines are too long

View File

@ -4340,6 +4340,7 @@ $LANG = array(
'no_available_methods' => 'We can\'t find any credit cards on your device. <a href="https://invoiceninja.github.io/docs/payments#apple-pay-google-pay-microsoft-pay" target="_blank" class="underline">Read more about this.</a>', 'no_available_methods' => 'We can\'t find any credit cards on your device. <a href="https://invoiceninja.github.io/docs/payments#apple-pay-google-pay-microsoft-pay" target="_blank" class="underline">Read more about this.</a>',
'gocardless_mandate_not_ready' => 'Payment mandate is not ready. Please try again later.', 'gocardless_mandate_not_ready' => 'Payment mandate is not ready. Please try again later.',
'payment_type_instant_bank_pay' => 'Instant Bank Pay', 'payment_type_instant_bank_pay' => 'Instant Bank Pay',
); );
return $LANG; return $LANG;

View File

@ -2,8 +2,8 @@
@import url($font_url); @import url($font_url);
:root { :root {
--primary-color: #298aab; --primary-color: $primary_color;
--secondary-color: #7081e0; --secondary-color: $secondary_color;
} }
body { body {
@ -149,13 +149,13 @@
#footer, #footer-spacer { #footer, #footer-spacer {
height: 220px; height: 220px;
padding: 1rem 3rem; padding: 1rem 1.5rem;
margin-top: 1rem; margin-top: 1rem;
} }
.footer-content { .footer-content {
display: flex; display: flex;
gap: 20px; gap: 10px;
width: 100%; width: 100%;
/* grid-template-columns: 1fr 1fr 1fr; */ /* grid-template-columns: 1fr 1fr 1fr; */
color: #fff4e9; color: #fff4e9;
@ -165,8 +165,8 @@
.footer-company-details-address-wrapper { .footer-company-details-address-wrapper {
display: flex; display: flex;
gap: 25px; gap: 5px;
margin-right: 150px; margin-right: 60px;
} }
#company-address, #company-address,
@ -348,7 +348,7 @@ $entity_images
<div id="footer"> <div id="footer">
<div class="footer-content"> <div class="footer-content">
<div> <div style="width: 70%;">
<p data-ref="total_table-footer">$entity_footer</p> <p data-ref="total_table-footer">$entity_footer</p>
<script> <script>

View File

@ -0,0 +1,126 @@
@extends('portal.ninja2020.layout.app')
@section('meta_title', ctrans('texts.account_management'))
@section('body')
<!-- This example requires Tailwind CSS v2.0+ -->
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{{ ctrans('texts.plan_status') }}
</h3>
</div>
<div class="border-t border-gray-200">
<dl>
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium text-gray-500">
{{ ctrans('texts.plan') }}
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
{{ $account->plan ? ucfirst($account->plan) : 'Free' }}
</dd>
</div>
@if($account->plan)
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium text-gray-500">
{{ ctrans('texts.expires') }}
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
{{ $client->formatDate($account->plan_expires, $client->date_format()) }}
</dd>
</div>
@if($account->plan == 'enterprise')
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium text-gray-500">
{{ ctrans('texts.users')}}
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
{{ $account->num_users }}
</dd>
</div>
@endif
@endif
@if($late_invoice)
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{{ ctrans('texts.invoice_status_id') }}
</h3>
<p class="mt-1 max-w-2xl text-sm text-gray-500">
{{ ctrans('texts.past_due') }}
</p>
</div>
<dl>
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium text-gray-500">
{{ ctrans('texts.invoice') }}
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
{{ $late_invoice->number }} - {{ \App\Utils\Number::formatMoney($late_invoice->balance, $client) }} <a class="button-link text-primary" href="/client/invoices/{{$late_invoice->hashed_id}}">{{ ctrans('texts.pay_now')}}</a>
</dd>
</div>
</dl>
@else
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium text-gray-500">
{{ ctrans('texts.plan_change') }}
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
<div>
<select id="newPlan" class="pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md">
<option value="">Select Plan</option>
@foreach($plans as $plan)
<option value="{{ $plan->hashed_id}}">{{ $plan->name }} {{ \App\Utils\Number::formatMoney($plan->promo_price, $client) }}</option>
@endforeach
</select>
@if($current_recurring_id)
<button id="handlePlanChange" class="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded">
{{ ctrans('texts.plan_change') }}
</button>
@else
<button id="handleNewPlan" class="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded">
{{ ctrans('texts.plan_upgrade') }}
</button>
@endif
</dd>
</div>
@endif
</dl>
</div>
</div>
@endsection
@push('footer')
<script type="text/javascript">
@if($current_recurring_id)
document.getElementById('handlePlanChange').addEventListener('click', function() {
if(document.getElementById("newPlan").value.length > 1)
location.href = 'https://invoiceninja.invoicing.co/client/subscriptions/{{ $current_recurring_id }}/plan_switch/' + document.getElementById("newPlan").value + '';
});
@else
document.getElementById('handleNewPlan').addEventListener('click', function() {
if(document.getElementById("newPlan").value.length > 1)
location.href = 'https://invoiceninja.invoicing.co/client/subscriptions/' + document.getElementById("newPlan").value + '/purchase';
});
@endif
</script>
@endpush

View File

@ -38,6 +38,8 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
Route::put('clients/{client}/upload', 'ClientController@upload')->name('clients.upload'); Route::put('clients/{client}/upload', 'ClientController@upload')->name('clients.upload');
Route::post('clients/bulk', 'ClientController@bulk')->name('clients.bulk'); Route::post('clients/bulk', 'ClientController@bulk')->name('clients.bulk');
Route::post('filters/{entity}', 'FilterController@index')->name('filters');
Route::resource('client_gateway_tokens', 'ClientGatewayTokenController'); Route::resource('client_gateway_tokens', 'ClientGatewayTokenController');
Route::post('connected_account', 'ConnectedAccountController@index'); Route::post('connected_account', 'ConnectedAccountController@index');

View File

@ -31,6 +31,8 @@ Route::get('client/ninja/{contact_key}/{company_key}', 'ClientPortal\NinjaPlanCo
Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence','domain_db'], 'prefix' => 'client', 'as' => 'client.'], function () { Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence','domain_db'], 'prefix' => 'client', 'as' => 'client.'], function () {
Route::get('dashboard', 'ClientPortal\DashboardController@index')->name('dashboard'); // name = (dashboard. index / create / show / update / destroy / edit Route::get('dashboard', 'ClientPortal\DashboardController@index')->name('dashboard'); // name = (dashboard. index / create / show / update / destroy / edit
Route::get('plan', 'ClientPortal\NinjaPlanController@plan')->name('plan'); // name = (dashboard. index / create / show / update / destroy / edit
Route::get('invoices', 'ClientPortal\InvoiceController@index')->name('invoices.index')->middleware('portal_enabled'); Route::get('invoices', 'ClientPortal\InvoiceController@index')->name('invoices.index')->middleware('portal_enabled');
Route::post('invoices/payment', 'ClientPortal\InvoiceController@bulk')->name('invoices.bulk'); Route::post('invoices/payment', 'ClientPortal\InvoiceController@bulk')->name('invoices.bulk');
Route::get('invoices/{invoice}', 'ClientPortal\InvoiceController@show')->name('invoice.show'); Route::get('invoices/{invoice}', 'ClientPortal\InvoiceController@show')->name('invoice.show');

View File

@ -0,0 +1,83 @@
<?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 Tests\Feature;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Tests\MockAccountData;
use Tests\TestCase;
/**
* @test
* @covers App\Http\Controllers\FilterController
*/
class FilterApiTest extends TestCase
{
use DatabaseTransactions;
use MockAccountData;
public function setUp() :void
{
parent::setUp();
$this->makeTestData();
$this->withoutMiddleware(
ThrottleRequests::class
);
}
public function testActivityGet()
{
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/filters/invoice');
$response->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/filters/quote');
$response->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/filters/credit');
$response->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/filters/payment');
$response->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/filters/recurring_invoice');
$response->assertStatus(200);
}
}