1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-29 20:57:11 +02:00

Merge pull request #5541 from turbo124/v5-stable

v5.1.54
This commit is contained in:
David Bomba 2021-04-27 21:28:15 +10:00 committed by GitHub
commit 4285ac7e3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 269715 additions and 268639 deletions

View File

@ -1 +1 @@
5.1.53 5.1.54

View File

@ -45,7 +45,7 @@ class EmailFailure
* *
* @var string * @var string
*/ */
public $string_metric5 = ''; public $string_metric5 = 'stub';
/** /**
* The exception string * The exception string
@ -53,5 +53,23 @@ class EmailFailure
* *
* @var string * @var string
*/ */
public $string_metric6 = ''; public $string_metric6 = 'stub';
/**
* The counter
* set to 1.
*
* @var string
*/
public $int_metric1 = 1;
/**
* Company Key
* @var string
*/
public $string_metric7 = '';
public function __construct($string_metric7) {
$this->string_metric7 = $string_metric7;
}
} }

View File

@ -0,0 +1,77 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\DataMapper\Analytics;
class EmailSuccess
{
/**
* The type of Sample.
*
* Monotonically incrementing counter
*
* - counter
*
* @var string
*/
public $type = 'mixed_metric';
/**
* The name of the counter.
* @var string
*/
public $name = 'job.success.email';
/**
* The datetime of the counter measurement.
*
* date("Y-m-d H:i:s")
*
* @var DateTime
*/
public $datetime;
/**
* The Class failure name
* set to 0.
*
* @var string
*/
public $string_metric5 = 'stub';
/**
* The exception string
* set to 0.
*
* @var string
*/
public $string_metric6 = 'stub';
/**
* The counter
* set to 1.
*
* @var string
*/
public $int_metric1 = 1;
/**
* Company Key
* @var string
*/
public $string_metric7 = '';
public function __construct($string_metric7) {
$this->string_metric7 = $string_metric7;
}
}

View File

@ -20,6 +20,7 @@ use App\Transformers\UserTransformer;
use Google_Client; use Google_Client;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
class ConnectedAccountController extends BaseController class ConnectedAccountController extends BaseController
{ {

View File

@ -0,0 +1,72 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use App\Models\CompanyToken;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use stdClass;
class LogoutController extends BaseController
{
public function __construct()
{
parent::__construct();
}
/**
* @OA\Post(
* path="/api/v1/logout",
* operationId="getLogout",
* tags={"logout"},
* summary="Gets a list of logout",
* description="Lists all logout",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(ref="#/components/parameters/index"),
* @OA\Response(
* response=200,
* description="Success message",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
* @param Request $request
* @return Response|mixed
*/
public function index(Request $request)
{
CompanyToken::with('company')
->whereRaw('BINARY `token`= ?', [$request->header('X-API-TOKEN')])
->company
->tokens()
->where('is_system', true)
->forceDelete();
return response()->json(['message' => 'All tokens deleted'], 200);
}
}

View File

@ -66,7 +66,7 @@ class SelfUpdateController extends BaseController
throw new FilePermissionsFailure('Cannot update system because files are not writable!'); throw new FilePermissionsFailure('Cannot update system because files are not writable!');
// Check if new version is available // Check if new version is available
if($updater->source()->isNewVersionAvailable()) { //if($updater->source()->isNewVersionAvailable()) {
// Get the new version available // Get the new version available
$versionAvailable = $updater->source()->getVersionAvailable(); $versionAvailable = $updater->source()->getVersionAvailable();
@ -76,7 +76,7 @@ class SelfUpdateController extends BaseController
$updater->source()->update($release); $updater->source()->update($release);
} //}
$cacheCompiled = base_path('bootstrap/cache/compiled.php'); $cacheCompiled = base_path('bootstrap/cache/compiled.php');
if (file_exists($cacheCompiled)) { unlink ($cacheCompiled); } if (file_exists($cacheCompiled)) { unlink ($cacheCompiled); }

View File

@ -47,8 +47,9 @@ class SetupController extends Controller
{ {
$check = SystemHealth::check(false); $check = SystemHealth::check(false);
if ($check['system_health'] == true && $check['simple_db_check'] && Schema::hasTable('accounts') && $account = Account::all()->first()) if ($check['system_health'] == true && $check['simple_db_check'] && Schema::hasTable('accounts') && $account = Account::all()->first()) {
return redirect('/'); return redirect('/');
}
// not sure if we really need this. // not sure if we really need this.
// if(File::exists(base_path('.env'))) // if(File::exists(base_path('.env')))
@ -124,20 +125,22 @@ class SetupController extends Controller
'MAIL_PASSWORD' => $request->input('mail_password'), 'MAIL_PASSWORD' => $request->input('mail_password'),
'NINJA_ENVIRONMENT' => 'selfhost', 'NINJA_ENVIRONMENT' => 'selfhost',
'DB_CONNECTION' => 'db-ninja-01',
]; ];
if(config('ninja.preconfigured_install')){ if (config('ninja.db.multi_db_enabled')) {
// Database connection was already configured. Don't let the user override it. $env_values['DB_CONNECTION'] = 'db-ninja-01';
unset($env_values['DB_HOST1']); }
unset($env_values['DB_PORT1']);
unset($env_values['DB_DATABASE1']); if (config('ninja.preconfigured_install')) {
unset($env_values['DB_USERNAME1']); // Database connection was already configured. Don't let the user override it.
unset($env_values['DB_PASSWORD1']); unset($env_values['DB_HOST1']);
} unset($env_values['DB_PORT1']);
unset($env_values['DB_DATABASE1']);
unset($env_values['DB_USERNAME1']);
unset($env_values['DB_PASSWORD1']);
}
try { try {
foreach ($env_values as $property => $value) { foreach ($env_values as $property => $value) {
$this->updateEnvironmentProperty($property, $value); $this->updateEnvironmentProperty($property, $value);
} }
@ -149,8 +152,9 @@ class SetupController extends Controller
DB::purge('db-ninja-01'); DB::purge('db-ninja-01');
/* Run migrations */ /* Run migrations */
if(!config('ninja.disable_auto_update')) if (!config('ninja.disable_auto_update')) {
Artisan::call('optimize'); Artisan::call('optimize');
}
Artisan::call('migrate', ['--force' => true]); Artisan::call('migrate', ['--force' => true]);
Artisan::call('db:seed', ['--force' => true]); Artisan::call('db:seed', ['--force' => true]);
@ -168,7 +172,6 @@ class SetupController extends Controller
return redirect('/'); return redirect('/');
} catch (Exception $e) { } catch (Exception $e) {
nlog($e->getMessage()); nlog($e->getMessage());
info($e->getMessage()); info($e->getMessage());
@ -278,9 +281,13 @@ class SetupController extends Controller
return redirect('/'); return redirect('/');
$cacheCompiled = base_path('bootstrap/cache/compiled.php'); $cacheCompiled = base_path('bootstrap/cache/compiled.php');
if (file_exists($cacheCompiled)) { unlink ($cacheCompiled); } if (file_exists($cacheCompiled)) {
unlink ($cacheCompiled);
}
$cacheServices = base_path('bootstrap/cache/services.php'); $cacheServices = base_path('bootstrap/cache/services.php');
if (file_exists($cacheServices)) { unlink ($cacheServices); } if (file_exists($cacheServices)) {
unlink ($cacheServices);
}
Artisan::call('clear-compiled'); Artisan::call('clear-compiled');
Artisan::call('cache:clear'); Artisan::call('cache:clear');

View File

@ -28,9 +28,8 @@ class SubdomainController extends BaseController
*/ */
public function index() public function index()
{ {
$subdomain_exists = MultiDB::findAndSetDbByDomain(request()->input('subdomain'));
if($subdomain_exists) if( MultiDB::findAndSetDbByDomain(request()->input('subdomain')) )
return response()->json(['message' => 'Domain not available'] , 401); return response()->json(['message' => 'Domain not available'] , 401);
return response()->json(['message' => 'Domain available'], 200); return response()->json(['message' => 'Domain available'], 200);

View File

@ -12,6 +12,7 @@
namespace App\Jobs\Mail; namespace App\Jobs\Mail;
use App\DataMapper\Analytics\EmailFailure; use App\DataMapper\Analytics\EmailFailure;
use App\DataMapper\Analytics\EmailSuccess;
use App\Events\Invoice\InvoiceWasEmailedAndFailed; use App\Events\Invoice\InvoiceWasEmailedAndFailed;
use App\Events\Payment\PaymentWasEmailedAndFailed; use App\Events\Payment\PaymentWasEmailedAndFailed;
use App\Jobs\Mail\NinjaMailerObject; use App\Jobs\Mail\NinjaMailerObject;
@ -86,8 +87,13 @@ class NinjaMailerJob implements ShouldQueue
//send email //send email
try { try {
nlog("trying to send"); nlog("trying to send");
Mail::to($this->nmo->to_user->email) Mail::to($this->nmo->to_user->email)
->send($this->nmo->mailable); ->send($this->nmo->mailable);
LightLogs::create(new EmailSuccess($this->nmo->company->company_key))
->batch();
} catch (\Exception $e) { } catch (\Exception $e) {
nlog("error failed with {$e->getMessage()}"); nlog("error failed with {$e->getMessage()}");
@ -198,9 +204,9 @@ class NinjaMailerJob implements ShouldQueue
nlog('mailer job failed'); nlog('mailer job failed');
nlog($exception->getMessage()); nlog($exception->getMessage());
$job_failure = new EmailFailure(); $job_failure = new EmailFailure($this->nmo->company->company_key);
$job_failure->string_metric5 = get_parent_class($this); $job_failure->string_metric5 = 'failed_email';
$job_failure->string_metric6 = $exception->getMessage(); $job_failure->string_metric6 = substr($exception->getMessage(), 0, 150);
LightLogs::create($job_failure) LightLogs::create($job_failure)
->batch(); ->batch();

View File

@ -192,6 +192,15 @@ class Import implements ShouldQueue
$array = json_decode(file_get_contents($this->file_path), 1); $array = json_decode(file_get_contents($this->file_path), 1);
$data = $array['data']; $data = $array['data'];
// disable some functionality here:
// 1. disable update_products
$update_product_state = $this->company->update_products;
$this->company->update_products = false;
$this->company->save();
foreach ($this->available_imports as $import) { foreach ($this->available_imports as $import) {
if (! array_key_exists($import, $data)) { if (! array_key_exists($import, $data)) {
//throw new ResourceNotAvailableForMigration("Resource {$key} is not available for migration."); //throw new ResourceNotAvailableForMigration("Resource {$key} is not available for migration.");
@ -211,8 +220,9 @@ class Import implements ShouldQueue
// $this->fixClientBalances(); // $this->fixClientBalances();
$check_data = CheckCompanyData::dispatchNow($this->company, md5(time())); $check_data = CheckCompanyData::dispatchNow($this->company, md5(time()));
// if($check_data['status'] == 'errors') //reset functionality here
// throw new ProcessingMigrationArchiveFailed(implode("\n", $check_data)); $this->company->update_products = $update_product_state;
$this->company->save();
try{ try{
Mail::to($this->user->email, $this->user->name()) Mail::to($this->user->email, $this->user->name())
@ -224,7 +234,12 @@ class Import implements ShouldQueue
/*After a migration first some basic jobs to ensure the system is up to date*/ /*After a migration first some basic jobs to ensure the system is up to date*/
VersionCheck::dispatch(); VersionCheck::dispatch();
CompanySizeCheck::dispatch();
//company size check
if ($this->company->invoices()->count() > 1000 || $this->company->products()->count() > 1000 || $this->company->clients()->count() > 1000) {
$company->is_large = true;
$company->save();
}
info('Completed🚀🚀🚀🚀🚀 at '.now()); info('Completed🚀🚀🚀🚀🚀 at '.now());

View File

@ -26,6 +26,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use ZipArchive; use ZipArchive;
class StartMigration implements ShouldQueue class StartMigration implements ShouldQueue
@ -110,6 +111,8 @@ class StartMigration implements ShouldQueue
Import::dispatchNow($file, $this->company, $this->user); Import::dispatchNow($file, $this->company, $this->user);
Storage::deleteDirectory(public_path("storage/migrations/{$filename}"));
} catch (NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing $e) { } catch (NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing $e) {
Mail::to($this->user)->send(new MigrationFailed($e, $e->getMessage())); Mail::to($this->user)->send(new MigrationFailed($e, $e->getMessage()));

View File

@ -16,6 +16,7 @@ use App\Models\ClientContact;
use App\Models\Company; use App\Models\Company;
use App\Models\CompanyToken; use App\Models\CompanyToken;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str; use Illuminate\Support\Str;
/** /**
@ -293,10 +294,17 @@ class MultiDB
{ {
/* This will set the database connection for the request */ /* This will set the database connection for the request */
config(['database.default' => $database]); config(['database.default' => $database]);
// for some reason this breaks everything _hard_
// DB::purge($database);
// DB::reconnect($database);
} }
public static function setDefaultDatabase() public static function setDefaultDatabase()
{ {
config(['database.default' => config('ninja.db.default')]); config(['database.default' => config('ninja.db.default')]);
// DB::purge(config('ninja.db.default'));
// DB::reconnect(config('ninja.db.default'));
} }
} }

View File

@ -0,0 +1,53 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Listeners\Account;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Libraries\MultiDB;
use App\Utils\Ninja;
use Illuminate\Contracts\Queue\ShouldQueue;
class CreateAccountActivity implements ShouldQueue
{
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct()
{
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
if(Ninja::isHosted())
{
$nmo = new NinjaMailerObject;
$nmo->mailable = new Modules\Admin\Mail\Welcome($event->user);
$nmo->company = $event->company;
$nmo->settings = $event->company->settings;
$nmo->to_user = $event->user;
NinjaMailerJob::dispatch($nmo);
}
}
}

View File

@ -269,15 +269,37 @@ class CompanyGateway extends BaseModel
*/ */
public function calcGatewayFeeLabel($amount, Client $client, $gateway_type_id = GatewayType::CREDIT_CARD) :string public function calcGatewayFeeLabel($amount, Client $client, $gateway_type_id = GatewayType::CREDIT_CARD) :string
{ {
$label = ''; $label = ' ';
$fee = $this->calcGatewayFee($amount, $gateway_type_id); $fee = $this->calcGatewayFee($amount, $gateway_type_id);
if ($fee > 0) { // if ($fee > 0) {
$fee = Number::formatMoney(round($fee, 2), $client); // $fee = Number::formatMoney(round($fee, 2), $client);
$label = ' - '.$fee.' '.ctrans('texts.fee'); // $label = ' - '.$fee.' '.ctrans('texts.fee');
// }
if($fee > 0) {
$fees_and_limits = $this->fees_and_limits->{$gateway_type_id};
if(strlen($fees_and_limits->fee_percent) >=1)
$label .= $fees_and_limits->fee_percent . '%';
if(strlen($fees_and_limits->fee_amount) >=1){
if(strlen($label) > 1) {
$label .= ' + ' . Number::formatMoney($fees_and_limits->fee_amount, $client);
}else {
$label .= Number::formatMoney($fees_and_limits->fee_amount, $client);
}
}
} }
return $label; return $label;
} }

View File

@ -74,6 +74,11 @@ class Task extends BaseModel
return $this->belongsTo(Client::class); return $this->belongsTo(Client::class);
} }
public function status()
{
return $this->belongsTo(TaskStatus::class);
}
public function invoice() public function invoice()
{ {
return $this->belongsTo(Invoice::class); return $this->belongsTo(Invoice::class);

View File

@ -271,7 +271,7 @@ class BaseDriver extends AbstractPaymentDriver
$invoices->each(function ($invoice) use ($fee_total) { $invoices->each(function ($invoice) use ($fee_total) {
if (collect($invoice->line_items)->contains('type_id', '3')) { if (collect($invoice->line_items)->contains('type_id', '3')) {
$invoice->service()->toggleFeesPaid()->save(); $invoice->service()->toggleFeesPaid()->deletePdf()->save();
$invoice->client->service()->updateBalance($fee_total)->save(); $invoice->client->service()->updateBalance($fee_total)->save();
$invoice->ledger()->updateInvoiceBalance($fee_total, "Gateway fee adjustment for invoice {$invoice->number}"); $invoice->ledger()->updateInvoiceBalance($fee_total, "Gateway fee adjustment for invoice {$invoice->number}");
} }
@ -370,6 +370,12 @@ class BaseDriver extends AbstractPaymentDriver
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->get(); $invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->get();
$invoices->each(function ($invoice){
$invoice->service()->deletePdf();
});
$invoices->first()->invitations->each(function ($invitation) use ($nmo){ $invoices->first()->invitations->each(function ($invitation) use ($nmo){
if ($invitation->contact->send_email && $invitation->contact->email) { if ($invitation->contact->send_email && $invitation->contact->email) {

View File

@ -11,6 +11,7 @@
namespace App\Providers; namespace App\Providers;
use App\Events\Account\AccountCreated;
use App\Events\Client\ClientWasArchived; use App\Events\Client\ClientWasArchived;
use App\Events\Client\ClientWasCreated; use App\Events\Client\ClientWasCreated;
use App\Events\Client\ClientWasDeleted; use App\Events\Client\ClientWasDeleted;
@ -179,6 +180,8 @@ class EventServiceProvider extends ServiceProvider
* @var array * @var array
*/ */
protected $listen = [ protected $listen = [
AccountCreated::class =>[
],
MessageSending::class =>[ MessageSending::class =>[
], ],
MessageSent::class => [ MessageSent::class => [

View File

@ -46,7 +46,11 @@ class MailServiceProvider extends MailProvider
protected function guzzle(array $config): HttpClient protected function guzzle(array $config): HttpClient
{ {
return new HttpClient($config); return new HttpClient(array_merge($config, [
'base_uri' => empty($config['base_uri'])
? 'https://api.postmarkapp.com'
: $config['base_uri']
]));
} }
public function provides() public function provides()
@ -56,4 +60,3 @@ class MailServiceProvider extends MailProvider
'mailer' ]; 'mailer' ];
} }
} }

View File

@ -22,6 +22,7 @@ class TaskRepository extends BaseRepository
{ {
use GeneratesCounter; use GeneratesCounter;
public $new_task = true;
/** /**
* Saves the task and its contacts. * Saves the task and its contacts.
@ -33,10 +34,15 @@ class TaskRepository extends BaseRepository
*/ */
public function save(array $data, Task $task) : ?Task public function save(array $data, Task $task) : ?Task
{ {
if($task->id)
$this->new_task = false;
$task->fill($data); $task->fill($data);
$task->save(); $task->save();
if($this->new_task && !$task->status_id)
$this->setDefaultStatus($task);
$task->number = empty($task->number) || !array_key_exists('number', $data) ? $this->getNextTaskNumber($task) : $data['number']; $task->number = empty($task->number) || !array_key_exists('number', $data) ? $this->getNextTaskNumber($task) : $data['number'];
if (isset($data['description'])) { if (isset($data['description'])) {
@ -103,6 +109,19 @@ class TaskRepository extends BaseRepository
} }
private function setDefaultStatus(Task $task)
{
$first_status = $task->company->task_statuses()
->whereNull('deleted_at')
->orderBy('id','asc')
->first();
if($first_status)
return $first_status->id;
return null;
}
/** /**
* Sorts the task status order IF the old status has changed between requests * Sorts the task status order IF the old status has changed between requests
* *

View File

@ -440,10 +440,10 @@ class Design extends BaseDesign
$elements = [ $elements = [
['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [ ['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [
['element' => 'p', 'content' => strtr($_variables['values']['$entity.public_notes'], $_variables), 'properties' => ['data-ref' => 'total_table-public_notes', 'style' => 'text-align: left;']],
['element' => 'p', 'content' => '', 'properties' => ['style' => 'text-align: left; display: flex; flex-direction: column;'], 'elements' => [ ['element' => 'p', 'content' => '', 'properties' => ['style' => 'text-align: left; display: flex; flex-direction: column;'], 'elements' => [
['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['hidden' => $this->entityVariableCheck('$entity.terms'), 'data-ref' => 'total_table-terms-label', 'style' => 'font-weight: bold; text-align: left; margin-top: 1rem;']], ['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['hidden' => $this->entityVariableCheck('$entity.terms'), 'data-ref' => 'total_table-terms-label', 'style' => 'font-weight: bold; text-align: left; margin-top: 1rem;']],
['element' => 'span', 'content' => strtr($_variables['values']['$entity.terms'], $_variables), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']], ['element' => 'span', 'content' => strtr($_variables['values']['$entity.terms'], $_variables), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']],
['element' => 'span', 'content' => strtr($_variables['values']['$entity_footer'], $_variables), 'properties' => ['data-ref' => 'total_table-footer', 'style' => 'text-align: left; margin-top: 1rem;']],
]], ]],
['element' => 'img', 'properties' => ['hidden' => $this->client->getSetting('signature_on_pdf'), 'style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature']], ['element' => 'img', 'properties' => ['hidden' => $this->client->getSetting('signature_on_pdf'), 'style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature']],
['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: flex; align-items: flex-start;'], 'elements' => [ ['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: flex; align-items: flex-start;'], 'elements' => [

View File

@ -187,33 +187,42 @@ trait DesignHelpers
// By default all table headers are hidden with HTML `hidden` property. // By default all table headers are hidden with HTML `hidden` property.
// This will check for table data values & if they're not empty it will remove hidden from the column itself. // This will check for table data values & if they're not empty it will remove hidden from the column itself.
/* document.querySelectorAll("tbody > tr > td").forEach(e => { /*
if ("" !== e.innerText) { document.addEventListener('DOMContentLoaded', function() {
let t = e.getAttribute("data-ref").slice(0, -3); document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(e => {
document.querySelector(`th[data-ref="${t}-th"]`).removeAttribute("hidden"); if ("" !== e.innerText) {
} let t = e.getAttribute("data-ref").slice(0, -3);
}); document.querySelector(`th[data-ref="${t}-th"]`).removeAttribute("hidden");
}
});
document.querySelectorAll("tbody > tr > td").forEach(e => { document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(e => {
let t = e.getAttribute("data-ref").slice(0, -3); let t = e.getAttribute("data-ref").slice(0, -3);
t = document.querySelector(`th[data-ref="${t}-th"]`); t = document.querySelector(`th[data-ref="${t}-th"]`);
if (!t.hasAttribute('hidden')) { if (!t.hasAttribute('hidden')) {
return; return;
} }
if ("" == e.innerText) { if ("" == e.innerText) {
e.setAttribute('hidden', 'true'); e.setAttribute('hidden', 'true');
} }
}); });
}, false);
*/ */
$javascript = 'document.querySelectorAll("tbody > tr > td").forEach(t=>{if(""!==t.innerText){let e=t.getAttribute("data-ref").slice(0,-3);document.querySelector(`th[data-ref="${e}-th"]`).removeAttribute("hidden")}}),document.querySelectorAll("tbody > tr > td").forEach(t=>{let e=t.getAttribute("data-ref").slice(0,-3);(e=document.querySelector(`th[data-ref="${e}-th"]`)).hasAttribute("hidden")&&""==t.innerText&&t.setAttribute("hidden","true")});'; $javascript = 'document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{if(""!==t.innerText){let e=t.getAttribute("data-ref").slice(0,-3);document.querySelector(`th[data-ref="${e}-th"]`).removeAttribute("hidden")}}),document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{let e=t.getAttribute("data-ref").slice(0,-3);(e=document.querySelector(`th[data-ref="${e}-th"]`)).hasAttribute("hidden")&&""==t.innerText&&t.setAttribute("hidden","true")})},!1);';
// Previously we've been decoding the HTML on the backend and XML parsing isn't good options because it requires, // Previously we've been decoding the HTML on the backend and XML parsing isn't good options because it requires,
// strict & valid HTML to even output/decode. Decoding is now done on the frontend with this piece of Javascript. // strict & valid HTML to even output/decode. Decoding is now done on the frontend with this piece of Javascript.
$html_decode = 'document.querySelectorAll(`[data-state="encoded-html"]`).forEach((element) => element.innerHTML = element.innerText)'; /**
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll(`[data-state="encoded-html"]`).forEach((element) => element.innerHTML = element.innerText)
}, false);
*/
$html_decode = 'document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll(`[data-state="encoded-html"]`).forEach(e=>e.innerHTML=e.innerText)},!1);';
return ['element' => 'div', 'elements' => [ return ['element' => 'div', 'elements' => [
['element' => 'script', 'content' => $javascript], ['element' => 'script', 'content' => $javascript],

53
composer.lock generated
View File

@ -51,16 +51,16 @@
}, },
{ {
"name": "aws/aws-sdk-php", "name": "aws/aws-sdk-php",
"version": "3.178.7", "version": "3.178.9",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/aws/aws-sdk-php.git", "url": "https://github.com/aws/aws-sdk-php.git",
"reference": "9443aecbf56eb43e0c7a409494ae4c09f4cebf5f" "reference": "89710500988a8a7d77f1282fcf6a1d0ad8297eaf"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/9443aecbf56eb43e0c7a409494ae4c09f4cebf5f", "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/89710500988a8a7d77f1282fcf6a1d0ad8297eaf",
"reference": "9443aecbf56eb43e0c7a409494ae4c09f4cebf5f", "reference": "89710500988a8a7d77f1282fcf6a1d0ad8297eaf",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -135,9 +135,9 @@
"support": { "support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues", "issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.178.7" "source": "https://github.com/aws/aws-sdk-php/tree/3.178.9"
}, },
"time": "2021-04-21T18:37:31+00:00" "time": "2021-04-23T18:28:02+00:00"
}, },
{ {
"name": "bacon/bacon-qr-code", "name": "bacon/bacon-qr-code",
@ -1091,40 +1091,39 @@
}, },
{ {
"name": "doctrine/cache", "name": "doctrine/cache",
"version": "1.10.2", "version": "1.11.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/cache.git", "url": "https://github.com/doctrine/cache.git",
"reference": "13e3381b25847283a91948d04640543941309727" "reference": "a9c1b59eba5a08ca2770a76eddb88922f504e8e0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/cache/zipball/13e3381b25847283a91948d04640543941309727", "url": "https://api.github.com/repos/doctrine/cache/zipball/a9c1b59eba5a08ca2770a76eddb88922f504e8e0",
"reference": "13e3381b25847283a91948d04640543941309727", "reference": "a9c1b59eba5a08ca2770a76eddb88922f504e8e0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "~7.1 || ^8.0" "php": "~7.1 || ^8.0"
}, },
"conflict": { "conflict": {
"doctrine/common": ">2.2,<2.4" "doctrine/common": ">2.2,<2.4",
"psr/cache": ">=3"
}, },
"require-dev": { "require-dev": {
"alcaeus/mongo-php-adapter": "^1.1", "alcaeus/mongo-php-adapter": "^1.1",
"doctrine/coding-standard": "^6.0", "cache/integration-tests": "dev-master",
"doctrine/coding-standard": "^8.0",
"mongodb/mongodb": "^1.1", "mongodb/mongodb": "^1.1",
"phpunit/phpunit": "^7.0", "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"predis/predis": "~1.0" "predis/predis": "~1.0",
"psr/cache": "^1.0 || ^2.0",
"symfony/cache": "^4.4 || ^5.2"
}, },
"suggest": { "suggest": {
"alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver"
}, },
"type": "library", "type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9.x-dev"
}
},
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
@ -1171,7 +1170,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/cache/issues", "issues": "https://github.com/doctrine/cache/issues",
"source": "https://github.com/doctrine/cache/tree/1.10.x" "source": "https://github.com/doctrine/cache/tree/1.11.0"
}, },
"funding": [ "funding": [
{ {
@ -1187,7 +1186,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-07-07T18:54:01+00:00" "time": "2021-04-13T14:46:17+00:00"
}, },
{ {
"name": "doctrine/dbal", "name": "doctrine/dbal",
@ -9627,16 +9626,16 @@
}, },
{ {
"name": "turbo124/beacon", "name": "turbo124/beacon",
"version": "1.0.8", "version": "1.0.10",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/turbo124/beacon.git", "url": "https://github.com/turbo124/beacon.git",
"reference": "22bc2c134efefd0f3c6c44b0c618cd4fa87b46d1" "reference": "91d9b06848f45ef9ed933ef3b99b77fcc107c9df"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/turbo124/beacon/zipball/22bc2c134efefd0f3c6c44b0c618cd4fa87b46d1", "url": "https://api.github.com/repos/turbo124/beacon/zipball/91d9b06848f45ef9ed933ef3b99b77fcc107c9df",
"reference": "22bc2c134efefd0f3c6c44b0c618cd4fa87b46d1", "reference": "91d9b06848f45ef9ed933ef3b99b77fcc107c9df",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -9684,9 +9683,9 @@
"turbo124" "turbo124"
], ],
"support": { "support": {
"source": "https://github.com/turbo124/beacon/tree/1.0.8" "source": "https://github.com/turbo124/beacon/tree/1.0.10"
}, },
"time": "2021-04-07T08:16:59+00:00" "time": "2021-04-25T01:37:06+00:00"
}, },
{ {
"name": "turbo124/laravel-gmail", "name": "turbo124/laravel-gmail",

View File

@ -13,7 +13,7 @@ return [
| |
*/ */
'default' => env('DB_CONNECTION', 'db-ninja-01'), 'default' => env('DB_CONNECTION', 'mysql'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -35,17 +35,18 @@ return [
// single database setup // single database setup
'mysql' => [ 'mysql' => [
'driver' => 'mysql', 'driver' => 'mysql',
'host' => env('DB_HOST1', '127.0.0.1'), 'host' => env('DB_HOST1', env('DB_HOST', '127.0.0.1')),
'database' => env('DB_DATABASE1', 'forge'), 'database' => env('DB_DATABASE1', env('DB_DATABASE', 'forge')),
'username' => env('DB_USERNAME1', 'forge'), 'username' => env('DB_USERNAME1', env('DB_USERNAME', 'forge')),
'password' => env('DB_PASSWORD1', ''), 'password' => env('DB_PASSWORD1', env('DB_PASSWORD', '')),
'port' => env('DB_PORT1', '3306'), 'port' => env('DB_PORT1', env('DB_PORT', '3306')),
'charset' => 'utf8mb4', 'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci', 'collation' => 'utf8mb4_unicode_ci',
'prefix' => '', 'prefix' => '',
'strict' => env('DB_STRICT', false), 'prefix_indexes' => true,
'engine' => 'InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8', 'strict' => env('DB_STRICT', false),
'engine' => 'InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8',
], ],
'sqlite' => [ 'sqlite' => [
@ -92,7 +93,7 @@ return [
'prefix' => '', 'prefix' => '',
'prefix_indexes' => true, 'prefix_indexes' => true,
'strict' => env('DB_STRICT', false), 'strict' => env('DB_STRICT', false),
'engine' => 'InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8', 'engine' => 'InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8',
], ],
'db-ninja-02' => [ 'db-ninja-02' => [
@ -107,7 +108,7 @@ return [
'prefix' => '', 'prefix' => '',
'prefix_indexes' => true, 'prefix_indexes' => true,
'strict' => env('DB_STRICT', false), 'strict' => env('DB_STRICT', false),
'engine' => 'InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8', 'engine' => 'InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8',
], ],
], ],

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', ''), 'app_domain' => env('APP_DOMAIN', ''),
'app_version' => '5.1.53', 'app_version' => '5.1.54',
'app_tag' => '5.1.53-release', 'app_tag' => '5.1.54-release',
'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', false), 'api_secret' => env('API_SECRET', false),

View File

@ -35,12 +35,12 @@ class ProductFactory extends Factory
'cost' => $this->faker->numberBetween(1, 1000), 'cost' => $this->faker->numberBetween(1, 1000),
'price' => $this->faker->numberBetween(1, 1000), 'price' => $this->faker->numberBetween(1, 1000),
'quantity' => $this->faker->numberBetween(1, 100), 'quantity' => $this->faker->numberBetween(1, 100),
'tax_name1' => 'GST', // 'tax_name1' => 'GST',
'tax_rate1' => 10, // 'tax_rate1' => 10,
'tax_name2' => 'VAT', // 'tax_name2' => 'VAT',
'tax_rate2' => 17.5, // 'tax_rate2' => 17.5,
'tax_name3' => 'THIRDTAX', // 'tax_name3' => 'THIRDTAX',
'tax_rate3' => 5, // 'tax_rate3' => 5,
'custom_value1' => $this->faker->text(20), 'custom_value1' => $this->faker->text(20),
'custom_value2' => $this->faker->text(20), 'custom_value2' => $this->faker->text(20),
'custom_value3' => $this->faker->text(20), 'custom_value3' => $this->faker->text(20),

View File

@ -20,12 +20,12 @@ class ConstantsSeeder extends Seeder
{ {
public function run() public function run()
{ {
Size::create(['name' => '1 - 3']); Size::create(['id' => 1, 'name' => '1 - 3']);
Size::create(['name' => '4 - 10']); Size::create(['id' => 2, 'name' => '4 - 10']);
Size::create(['name' => '11 - 50']); Size::create(['id' => 3, 'name' => '11 - 50']);
Size::create(['name' => '51 - 100']); Size::create(['id' => 4, 'name' => '51 - 100']);
Size::create(['name' => '101 - 500']); Size::create(['id' => 5, 'name' => '101 - 500']);
Size::create(['name' => '500+']); Size::create(['id' => 6, 'name' => '500+']);
PaymentLibrary::create(['name' => 'Omnipay']); PaymentLibrary::create(['name' => 'Omnipay']);

View File

@ -9,7 +9,7 @@ const RESOURCES = {
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed", "icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35", "icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
"manifest.json": "ce1b79950eb917ea619a0a30da27c6a3", "manifest.json": "ce1b79950eb917ea619a0a30da27c6a3",
"main.dart.js": "36003565795043e387fce59a2259958d", "main.dart.js": "6c1bdcfdb9b47f1c487a57ad331504db",
"assets/NOTICES": "3bf0be7e0e4deca198e5f5c4800f9232", "assets/NOTICES": "3bf0be7e0e4deca198e5f5c4800f9232",
"assets/fonts/MaterialIcons-Regular.otf": "1288c9e28052e028aba623321f7826ac", "assets/fonts/MaterialIcons-Regular.otf": "1288c9e28052e028aba623321f7826ac",
"assets/AssetManifest.json": "659dcf9d1baf3aed3ab1b9c42112bf8f", "assets/AssetManifest.json": "659dcf9d1baf3aed3ab1b9c42112bf8f",

180717
public/main.dart.js vendored

File diff suppressed because one or more lines are too long

180725
public/main.foss.dart.js vendored

File diff suppressed because one or more lines are too long

176324
public/main.wasm.dart.js vendored

File diff suppressed because one or more lines are too long

View File

@ -216,7 +216,7 @@
color: white; color: white;
} }
[data-ref="total_table-public_notes"] { [data-ref="total_table-footer"] {
padding-top: 0.5rem padding-top: 0.5rem
} }
</style> </style>
@ -239,7 +239,7 @@
<td class="page-footer-cell"> <td class="page-footer-cell">
<div class="footer-wrapper" id="footer"> <div class="footer-wrapper" id="footer">
<div> <div>
<p data-ref="total_table-public_notes">$entity.public_notes</p> <p data-ref="total_table-footer">$entity_footer</p>
</div> </div>
<div> <!-- #2 column --> </div> <div> <!-- #2 column --> </div>
<div> <!-- #3 column --> </div> <div> <!-- #3 column --> </div>

View File

@ -219,7 +219,7 @@
padding-right: 1rem; padding-right: 1rem;
} }
[data-ref="total_table-public_notes"] { [data-ref="total_table-footer"] {
padding-left: 1rem padding-left: 1rem
} }
</style> </style>
@ -257,5 +257,5 @@
</div> </div>
<div id="footer"> <div id="footer">
<p data-ref="total_table-public_notes">$entity.public_notes</p> <p data-ref="total_table-footer">$entity_footer</p>
</div> </div>

View File

@ -187,7 +187,7 @@
padding-right: 1rem; padding-right: 1rem;
} }
[data-ref="total_table-public_notes"] { [data-ref="total_table-footer"] {
padding-left: 1rem padding-left: 1rem
} }
</style> </style>
@ -223,5 +223,5 @@
</div> </div>
<div id="footer"> <div id="footer">
<p data-ref="total_table-public_notes">$entity.public_notes</p> <p data-ref="total_table-footer">$entity_footer</p>
</div> </div>

View File

@ -179,7 +179,7 @@
padding-right: 1rem; padding-right: 1rem;
} }
[data-ref="total_table-public_notes"] { [data-ref="total_table-footer"] {
padding-left: 1rem padding-left: 1rem
} }
</style> </style>
@ -221,5 +221,5 @@
</div> </div>
<div id="footer"> <div id="footer">
<p data-ref="total_table-public_notes">$entity.public_notes</p> <p data-ref="total_table-footer">$entity_footer</p>
</div> </div>

View File

@ -173,7 +173,7 @@
padding-right: 0.5rem; padding-right: 0.5rem;
} }
[data-ref="total_table-public_notes"] { [data-ref="total_table-footer"] {
padding-left: 1rem padding-left: 1rem
} }
</style> </style>
@ -221,6 +221,6 @@
</div> </div>
<div id="footer"> <div id="footer">
<p data-ref="total_table-public_notes">$entity.public_notes</p> <p data-ref="total_table-footer">$entity_footer</p>
</div> </div>

View File

@ -258,5 +258,5 @@
</div> </div>
<div id="footer"> <div id="footer">
<p data-ref="total_table-public_notes">$entity.public_notes</p> <p data-ref="total_table-footer">$entity_footer</p>
</div> </div>

View File

@ -223,7 +223,7 @@
padding: 1rem; padding: 1rem;
} }
[data-ref="total_table-public_notes"] { [data-ref="total_table-footer"] {
margin-top: 2rem; margin-top: 2rem;
margin-bottom: 2rem margin-bottom: 2rem
} }
@ -248,7 +248,7 @@
<div class="footer-wrapper" id="footer"> <div class="footer-wrapper" id="footer">
<div class="footer-content"> <div class="footer-content">
<div> <div>
<p data-ref="total_table-public_notes">$entity.public_notes</p> <p data-ref="total_table-footer">$entity_footer</p>
</div> </div>
<div id="company-details"></div> <div id="company-details"></div>
<div id="company-address"></div> <div id="company-address"></div>

View File

@ -151,7 +151,7 @@
padding-right: 1rem; padding-right: 1rem;
} }
[data-ref="total_table-public_notes"] { [data-ref="total_table-footer"] {
padding-left: 1rem padding-left: 1rem
} }
</style> </style>
@ -185,5 +185,5 @@
</div> </div>
<div id="footer"> <div id="footer">
<p data-ref="total_table-public_notes">$entity.public_notes</p> <p data-ref="total_table-footer">$entity_footer</p>
</div> </div>

View File

@ -222,7 +222,7 @@
padding: 10px; padding: 10px;
} }
[data-ref="total_table-public_notes"] { [data-ref="total_table-footer"] {
text-align: left; text-align: left;
padding-left: 3rem; padding-left: 3rem;
padding-right: 3rem; padding-right: 3rem;
@ -281,6 +281,6 @@
</div> </div>
<div id="footer"> <div id="footer">
<p data-ref="total_table-public_notes">$entity.public_notes</p> <p data-ref="total_table-footer">$entity_footer/p>
</div> </div>

View File

@ -20,15 +20,13 @@
@endif @endif
</div> </div>
@endforeach @endforeach
<p class="mt-4 block text-sm text-gray-900">{{ ctrans('texts.click_agree_to_accept_terms') }}</p>
</div> </div>
</div> </div>
</div> </div>
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse"> <div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<div class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"> <div class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
<button type="button" id="accept-terms-button" class="button button-primary bg-primary"> <button type="button" id="accept-terms-button" class="button button-primary bg-primary">
{{ ctrans('texts.agree') }} {{ ctrans('texts.i_agree') }}
</button> </button>
</div> </div>
<div class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"> <div class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto">

View File

@ -91,6 +91,8 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
Route::get('invoice/{invitation_key}/download', 'InvoiceController@downloadPdf')->name('invoices.downloadPdf'); Route::get('invoice/{invitation_key}/download', 'InvoiceController@downloadPdf')->name('invoices.downloadPdf');
Route::post('invoices/bulk', 'InvoiceController@bulk')->name('invoices.bulk'); Route::post('invoices/bulk', 'InvoiceController@bulk')->name('invoices.bulk');
Route::post('logout', 'LogoutController@index')->name('logout');
Route::post('migrate', 'MigrationController@index')->name('migrate.start'); Route::post('migrate', 'MigrationController@index')->name('migrate.start');
Route::post('migration/purge/{company}', 'MigrationController@purgeCompany')->middleware('password_protected'); Route::post('migration/purge/{company}', 'MigrationController@purgeCompany')->middleware('password_protected');