1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 21:22:58 +01:00

Merge pull request #6294 from turbo124/v5-develop

Add net_subtotal option
This commit is contained in:
David Bomba 2021-07-19 17:39:55 +10:00 committed by GitHub
commit e65016d2dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 320 additions and 25 deletions

View File

@ -663,6 +663,7 @@ class CompanySettings extends BaseSettings
'$task.line_total',
],
'total_columns' => [
'$net_subtotal',
'$subtotal',
'$discount',
'$custom_surcharge1',

View File

@ -16,6 +16,7 @@ use App\DataMapper\Analytics\LoginSuccess;
use App\Events\User\UserLoggedIn;
use App\Http\Controllers\BaseController;
use App\Http\Controllers\Controller;
use App\Http\Requests\Login\LoginRequest;
use App\Jobs\Account\CreateAccount;
use App\Jobs\Company\CreateCompanyToken;
use App\Jobs\Util\SystemLogger;
@ -156,7 +157,7 @@ class LoginController extends BaseController
* ),
* )
*/
public function apiLogin(Request $request)
public function apiLogin(LoginRequest $request)
{
$this->forced_includes = ['company_users'];

View File

@ -25,7 +25,10 @@ use App\Jobs\Company\CreateCompany;
use App\Jobs\Company\CreateCompanyPaymentTerms;
use App\Jobs\Company\CreateCompanyTaskStatuses;
use App\Jobs\Company\CreateCompanyToken;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Jobs\Ninja\RefundCancelledAccount;
use App\Mail\Company\CompanyDeleted;
use App\Models\Account;
use App\Models\Company;
use App\Models\CompanyUser;
@ -504,6 +507,15 @@ class CompanyController extends BaseController
$company->company_users->each(function ($company_user){
$company_user->forceDelete();
});
$other_company = $company->account->companies->where('id', '!=', $company->id)->first();
$nmo = new NinjaMailerObject;
$nmo->mailable = new CompanyDeleted($company->present()->name, auth()->user(), $company->account, $company->settings);
$nmo->company = $other_company;
$nmo->settings = $other_company->settings;
$nmo->to_user = auth()->user();
NinjaMailerJob::dispatch($nmo);
$company->delete();

View File

@ -44,6 +44,14 @@ class PasswordProtection
else
$timeout = $timeout/1000;
//test if password if base64 encoded
$x_api_password = $request->header('X-API-PASSWORD');
if($request->header('X-API-PASSWORD-BASE64'))
{
$x_api_password = base64_decode($request->header('X-API-PASSWORD-BASE64'));
}
if (Cache::get(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in')) {
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
@ -66,7 +74,7 @@ class PasswordProtection
];
//If OAuth and user also has a password set - check both
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->company()->oauth_password_required && auth()->user()->has_password && Hash::check(auth()->user()->password, $request->header('X-API-PASSWORD'))) {
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->company()->oauth_password_required && auth()->user()->has_password && Hash::check(auth()->user()->password, $x_api_password)) {
nlog("existing user with password");
@ -86,7 +94,7 @@ class PasswordProtection
return response()->json($error, 412);
}elseif ($request->header('X-API-PASSWORD') && Hash::check($request->header('X-API-PASSWORD'), auth()->user()->password)) {
}elseif ($x_api_password && Hash::check($x_api_password, auth()->user()->password)) {
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);

View File

@ -0,0 +1,54 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Requests\Login;
use App\Http\Requests\Request;
class LoginRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'email' => 'required',
'password' => 'required',
];
}
protected function prepareForValidation()
{
$input = $this->all();
// if(base64_decode(base64_encode($input['password'])) === $input['password'])
// $input['password'] = base64_decode($input['password']);
// nlog($input['password']);
$this->replace($input);
}
}

View File

@ -47,8 +47,6 @@ class InvitationViewedListener implements ShouldQueue
$entity_name = lcfirst(class_basename($event->entity));
$invitation = $event->invitation;
// $notification = new EntityViewedNotification($invitation, $entity_name);
$nmo = new NinjaMailerObject;
$nmo->mailable = new NinjaMailer( (new EntityViewedObject($invitation, $entity_name))->build() );
$nmo->company = $invitation->company;
@ -57,8 +55,9 @@ class InvitationViewedListener implements ShouldQueue
foreach ($invitation->company->company_users as $company_user) {
$entity_viewed = "{$entity_name}_viewed";
$entity_viewed_all = "{$entity_name}_viewed_all";
$methods = $this->findUserNotificationTypes($invitation, $company_user, $entity_name, ['all_notifications', $entity_viewed]);
$methods = $this->findUserNotificationTypes($invitation, $company_user, $entity_name, ['all_notifications', $entity_viewed, $entity_viewed_all]);
if (($key = array_search('mail', $methods)) !== false) {
unset($methods[$key]);
@ -68,16 +67,7 @@ class InvitationViewedListener implements ShouldQueue
}
// $notification->method = $methods;
// $company_user->user->notify($notification);
}
// if (isset($invitation->company->slack_webhook_url)) {
// $notification->method = ['slack'];
// Notification::route('slack', $invitation->company->slack_webhook_url)
// ->notify($notification);
// }
}
}

View File

@ -16,6 +16,7 @@ use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Libraries\MultiDB;
use App\Mail\Admin\VerifyUserObject;
use App\Mail\User\UserAdded;
use App\Notifications\Ninja\VerifyUser;
use App\Utils\Ninja;
use Exception;
@ -52,5 +53,13 @@ class SendVerificationNotification implements ShouldQueue
$event->user->service()->invite($event->company);
$nmo = new NinjaMailerObject;
$nmo->mailable = new UserAdded($event->company, $event->creating_user, $event->user);
$nmo->company = $event->company;
$nmo->settings = $event->company->settings;
$nmo->to_user = $event->creating_user;
NinjaMailerJob::dispatch($nmo);
}
}

View File

@ -0,0 +1,61 @@
<?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\Mail\Company;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class CompanyDeleted extends Mailable
{
// use Queueable, SerializesModels;
public $account;
public $company;
public $user;
public $settings;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct($company, $user, $account, $settings)
{
$this->company = $company;
$this->user = $user;
$this->account = $account;
$this->settings = $settings;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->from(config('mail.from.address'), config('mail.from.name'))
->subject(ctrans('texts.company_deleted'))
->view('email.admin.company_deleted')
->with([
'settings' => $this->settings,
'logo' => '',
'title' => ctrans('texts.company_deleted'),
'body' => ctrans('texts.company_deleted_body', ['company' => $this->company, 'user' => $this->user->present()->name(), 'time' => now()]),
'whitelabel' => $this->account->isPaid(),
]);
}
}

View File

@ -0,0 +1,59 @@
<?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\Mail\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class UserAdded extends Mailable
{
// use Queueable, SerializesModels;
public $company;
public $user;
public $created_user;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct($company, $user, $created_user)
{
$this->company = $company;
$this->user = $user;
$this->created_user = $created_user;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->from(config('mail.from.address'), config('mail.from.name'))
->subject(ctrans('texts.created_user'))
->view('email.admin.user_added')
->with([
'settings' => $this->company->settings,
'logo' => $this->company->present()->logo(),
'title' => ctrans('texts.created_user'),
'body' => ctrans('texts.user_created_user', ['user' => $this->user->present()->name(), 'created_user' => $this->created_user->present()->name(), 'time' => now()]),
'whitelabel' => $this->company->account->isPaid(),
]);
}
}

View File

@ -48,7 +48,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'after_invoice_date' &&
$this->settings->num_days_reminder1 > 0) {
$this->settings->enable_reminder1) {
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
@ -57,7 +57,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'before_due_date' &&
$this->settings->num_days_reminder1 > 0) {
$this->settings->enable_reminder1) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder1)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
@ -66,7 +66,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'after_due_date' &&
$this->settings->num_days_reminder1 > 0) {
$this->settings->enable_reminder1) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
@ -75,7 +75,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'after_invoice_date' &&
$this->settings->num_days_reminder2 > 0) {
$this->settings->enable_reminder2) {
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
@ -84,7 +84,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'before_due_date' &&
$this->settings->num_days_reminder2 > 0) {
$this->settings->enable_reminder2) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder2)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
@ -93,7 +93,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'after_due_date' &&
$this->settings->num_days_reminder2 > 0) {
$this->settings->enable_reminder2) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
@ -102,7 +102,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'after_invoice_date' &&
$this->settings->num_days_reminder3 > 0) {
$this->settings->enable_reminder3) {
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
@ -111,7 +111,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'before_due_date' &&
$this->settings->num_days_reminder3 > 0) {
$this->settings->enable_reminder3) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
@ -120,7 +120,7 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'after_due_date' &&
$this->settings->num_days_reminder3 > 0) {
$this->settings->enable_reminder3) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
@ -128,7 +128,7 @@ class UpdateReminder extends AbstractService
}
if ($this->invoice->last_sent_date &&
(int)$this->settings->endless_reminder_frequency_id > 0) {
$this->settings->enable_reminder_endless) {
$reminder_date = $this->addTimeInterval($this->invoice->last_sent_date, (int)$this->settings->endless_reminder_frequency_id)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));

View File

@ -51,6 +51,7 @@ class DesignTransformer extends EntityTransformer
'archived_at' => (int) $design->deleted_at,
'created_at' => (int) $design->created_at,
'is_deleted' => (bool) $design->is_deleted,
'is_free' => ($design->id <= 4) ? true : false,
];
}
}

View File

@ -167,6 +167,7 @@ class HtmlEngine
$data['$invoice.discount'] = ['value' => Number::formatMoney($this->entity_calc->getTotalDiscount(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.discount')];
$data['$discount'] = &$data['$invoice.discount'];
$data['$subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.subtotal')];
$data['$net_subtotal'] = ['value' => Number::formatMoney(($this->entity_calc->getSubTotal() - $this->entity->total_taxes), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.subtotal')];
$data['$invoice.subtotal'] = &$data['$subtotal'];
if ($this->entity->partial > 0) {

View File

@ -170,4 +170,29 @@ class Ninja
// return implode('-', $parts);
// }
//
/*
* Available - but not recommended for use
*
* This will guarantee a given string IS the correct format for a
* base64 encoded string ,
* but can't guarantee that it is a base64 encoded string
*
*/
public static function isBase64Encoded(string $s) : bool
{
// Check if there are valid base64 characters
if (!preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $s)) return false;
// Decode the string in strict mode and check the results
$decoded = base64_decode($s, true);
if(false === $decoded) return false;
// if string returned contains not printable chars
if (0 < preg_match('/((?![[:graph:]])(?!\s)(?!\p{L}))./', $decoded, $matched)) return false;
// Encode the string again
if(base64_encode($decoded) != $s) return false;
return true;
}
}

View File

@ -83,6 +83,7 @@ class SystemHealth
'flutter_renderer' => (string)config('ninja.flutter_canvas_kit'),
'jobs_pending' => (int) Queue::size(),
'pdf_engine' => (string) self::getPdfEngine(),
'queue' => (string) config('queue.default'),
];
}

View File

@ -4281,6 +4281,9 @@ $LANG = array(
'quotes_with_status_sent_can_be_approved' => 'Only quotes with "Sent" status can be approved.',
'no_quotes_available_for_download' => 'No quotes available for download.',
'copyright' => 'Copyright',
'user_created_user' => ':user created :created_user at :time',
'company_deleted' => 'Company deleted',
'company_deleted_body' => 'Company [ :company ] was deleted by :user',
);
return $LANG;

View File

@ -0,0 +1,6 @@
@component('email.template.admin', ['logo' => $logo, 'settings' => $settings])
<div class="center">
<h1>{!! $title !!}</h1>
<p>{!! $body !!}</p>
</div>
@endcomponent

View File

@ -0,0 +1,6 @@
@component('email.template.admin', ['logo' => $logo, 'settings' => $settings])
<div class="center">
<h1>{!! $title !!}</h1>
<p>{!! $body !!}</p>
</div>
@endcomponent

57
tests/Unit/Base64Test.php Normal file
View File

@ -0,0 +1,57 @@
<?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\Unit;
use App\Utils\Ninja;
use Tests\TestCase;
/**
* @test
*/
class Base64Test extends TestCase
{
/**
* Important consideration with Base64
* encoding checks.
*
* No method can guarantee against false positives.
*/
public function setUp() :void
{
parent::setUp();
}
public function testBadBase64String()
{
$this->assertFalse(Ninja::isBase64Encoded('x'));
}
public function testCorrectBase64Encoding()
{
$this->assertTrue(Ninja::isBase64Encoded('MTIzNDU2'));
}
public function testBadBase64StringScenaro1()
{
$this->assertFalse(Ninja::isBase64Encoded('Matthies'));
}
public function testBadBase64StringScenaro2()
{
$this->assertFalse(Ninja::isBase64Encoded('Barthels'));
}
public function testBadBase64StringScenaro3()
{
$this->assertFalse(Ninja::isBase64Encoded('aaa'));
}
}