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

Merge pull request #8274 from turbo124/v5-develop

Clean up for bank integration bulk actions
This commit is contained in:
David Bomba 2023-02-10 08:20:41 +11:00 committed by GitHub
commit 38141754d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 478 additions and 37 deletions

View File

@ -0,0 +1,19 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;
use Exception;
class ClientHostedMigrationException extends Exception
{
// ..
}

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;

View File

@ -1,4 +1,14 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Exceptions;

View File

@ -291,11 +291,11 @@ class LoginController extends BaseController
return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400);
}
// $cu->first()->account->companies->each(function ($company) use ($cu, $request) {
// if ($company->tokens()->where('is_system', true)->count() == 0) {
// (new CreateCompanyToken($company, $cu->first()->user, $request->server('HTTP_USER_AGENT')))->handle();
// }
// });
$cu->first()->account->companies->each(function ($company) use ($cu, $request) {
if ($company->tokens()->where('is_system', true)->count() == 0) {
(new CreateCompanyToken($company, $cu->first()->user, $request->server('HTTP_USER_AGENT')))->handle();
}
});
if ($request->has('current_company') && $request->input('current_company') == 'true') {
$cu->where('company_id', $company_token->company_id);
@ -480,13 +480,13 @@ class LoginController extends BaseController
return $cu;
}
// if (auth()->user()->company_users()->count() != auth()->user()->tokens()->distinct('company_id')->count()) {
// auth()->user()->companies->each(function ($company) {
// if (!CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $company->id)->exists()) {
// (new CreateCompanyToken($company, auth()->user(), 'Google_O_Auth'))->handle();
// }
// });
// }
if (auth()->user()->company_users()->count() != auth()->user()->tokens()->distinct('company_id')->count()) {
auth()->user()->companies->each(function ($company) {
if (!CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $company->id)->exists()) {
(new CreateCompanyToken($company, auth()->user(), 'Google_O_Auth'))->handle();
}
});
}
$truth->setCompanyToken(CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $set_company->id)->first());

View File

@ -472,10 +472,12 @@ class BankIntegrationController extends BaseController
$ids = request()->input('ids');
$bank_integrations = BankIntegration::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get();
$bank_integrations = BankIntegration::withTrashed()->whereIn('id', $this->transformKeys($ids))
->company()
->cursor()
->each(function ($bank_integration, $key) use ($action) {
$bank_integrations->each(function ($bank_integration, $key) use ($action) {
$this->bank_integration_repo->{$action}($bank_integration);
$this->bank_integration_repo->{$action}($bank_integration);
});
/* Need to understand which permission are required for the given bulk action ie. view / edit */

View File

@ -17,6 +17,7 @@ use App\Events\PurchaseOrder\PurchaseOrderWasUpdated;
use App\Factory\PurchaseOrderFactory;
use App\Filters\PurchaseOrderFilters;
use App\Http\Requests\PurchaseOrder\ActionPurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\BulkPurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\CreatePurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\DestroyPurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\EditPurchaseOrderRequest;
@ -475,12 +476,12 @@ class PurchaseOrderController extends BaseController
* ),
* )
*/
public function bulk()
public function bulk(BulkPurchaseOrderRequest $request)
{
$action = request()->input('action');
$action = $request->input('action');
$ids = request()->input('ids');
$ids = $request->input('ids');
if(Ninja::isHosted() && (stripos($action, 'email') !== false) && !auth()->user()->company()->account->account_sms_verified)
return response(['message' => 'Please verify your account to send emails.'], 400);
@ -497,7 +498,6 @@ class PurchaseOrderController extends BaseController
if ($action == 'bulk_download' && $purchase_orders->count() >= 1) {
$purchase_orders->each(function ($purchase_order) {
if (auth()->user()->cannot('view', $purchase_order)) {
nlog("access denied");
return response()->json(['message' => ctrans('text.access_denied')]);
}
});

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Factory\TaskStatusFactory;
use App\Filters\TaskStatusFilters;
use App\Http\Requests\TaskStatus\ActionTaskStatusRequest;
use App\Http\Requests\TaskStatus\CreateTaskStatusRequest;
use App\Http\Requests\TaskStatus\DestroyTaskStatusRequest;
use App\Http\Requests\TaskStatus\ShowTaskStatusRequest;
@ -449,18 +450,20 @@ class TaskStatusController extends BaseController
* ),
* )
*/
public function bulk()
public function bulk(ActionTaskStatusRequest $request)
{
$action = request()->input('action');
$action = $request->input('action');
$ids = request()->input('ids');
$ids = $request->input('ids');
$task_status = TaskStatus::withTrashed()->company()->find($this->transformKeys($ids));
TaskStatus::withTrashed()
->company()
->whereIn('id', $this->transformKeys($ids))
->cursor()
->each(function ($task_status, $key) use ($action) {
$task_status->each(function ($task_status, $key) use ($action) {
if (auth()->user()->can('edit', $task_status)) {
$this->task_status_repo->{$action}($task_status);
}
});
return $this->listResponse(TaskStatus::withTrashed()->whereIn('id', $this->transformKeys($ids)));

View File

@ -12,21 +12,16 @@
namespace App\Http\Requests\PurchaseOrder;
use App\Http\Requests\Request;
use App\Models\PurchaseOrder;
use App\Utils\Traits\MakesHash;
class ActionPurchaseOrderRequest extends Request
{
use MakesHash;
private $error_msg;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
private $error_msg;
// private $invoice;
public function authorize() : bool
{

View File

@ -0,0 +1,38 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Requests\PurchaseOrder;
use App\Http\Requests\Request;
class BulkPurchaseOrderRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return true;
}
public function rules()
{
return [
'ids' => 'required|bail|array|min:1',
'action' => 'in:archive,restore,delete,email,bulk_download,bulk_print,mark_sent,download,send_email,add_to_inventory,expense,cancel'
];
}
}

View File

@ -24,4 +24,14 @@ class ActionTaskStatusRequest extends Request
{
return auth()->user()->isAdmin();
}
public function rules()
{
return [
'ids' => 'required|bail|array',
'action' => 'in:archive,restore,delete'
];
}
}

View File

@ -13,6 +13,7 @@ namespace App\Jobs\Util;
use App\DataMapper\Analytics\MigrationFailure;
use App\DataMapper\CompanySettings;
use App\Exceptions\ClientHostedMigrationException;
use App\Exceptions\MigrationValidatorFailed;
use App\Exceptions\ProcessingMigrationArchiveFailed;
use App\Exceptions\ResourceDependencyMissing;
@ -582,18 +583,42 @@ class Import implements ShouldQueue
$validator = null;
}
private function testUserDbLocationSanity(array $data): bool
{
if(Ninja::isSelfHost())
return true;
$current_db = config('database.default');
$db1_count = User::on('db-ninja-01')->withTrashed()->whereIn('email', array_column($data, 'email'))->count();
$db2_count = User::on('db-ninja-02')->withTrashed()->whereIn('email', array_column($data, 'email'))->count();
MultiDB::setDb($current_db);
if($db2_count == 0 && $db1_count == 0)
return true;
if($db1_count >= 1 && $db2_count >= 1)
return false;
return true;
}
/**
* @param array $data
* @throws Exception
*/
private function processUsers(array $data): void
{
if(!$this->testUserDbLocationSanity($data))
throw new ClientHostedMigrationException('You have users that belong to different accounts registered in the system, please contact us to resolve.', 400);
User::unguard();
$rules = [
'*.first_name' => ['string'],
'*.last_name' => ['string'],
//'*.email' => ['distinct'],
'*.email' => ['distinct', 'email', new ValidUserForCompany()],
];
@ -749,7 +774,7 @@ class Import implements ShouldQueue
Client::reguard();
Client::with('contacts')->where('company_id', $this->company->id)->cursor()->each(function ($client){
Client::withTrashed()->with('contacts')->where('company_id', $this->company->id)->cursor()->each(function ($client){
$contact = $client->contacts->sortByDesc('is_primary')->first();
$contact->is_primary = true;

View File

@ -11,6 +11,7 @@
namespace App\Jobs\Util;
use App\Exceptions\ClientHostedMigrationException;
use App\Exceptions\MigrationValidatorFailed;
use App\Exceptions\NonExistingMigrationFile;
use App\Exceptions\ProcessingMigrationArchiveFailed;
@ -126,7 +127,7 @@ class StartMigration implements ShouldQueue
App::forgetInstance('translator');
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
} catch (NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing | \Exception $e) {
} catch (ClientHostedMigrationException | NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing | \Exception $e) {
$this->company->update_products = $update_product_flag;
$this->company->save();

View File

@ -1,7 +1,18 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Mail;
use App\Exceptions\ClientHostedMigrationException;
use App\Models\Company;
use Illuminate\Mail\Mailable;
use Illuminate\Support\Facades\App;
@ -37,11 +48,17 @@ class MigrationFailed extends Mailable
public function build()
{
App::setLocale($this->company->getLocale());
$special_message = '';
if($this->exception instanceof ClientHostedMigrationException)
$special_message = $this->content;
return $this
->from(config('mail.from.address'), config('mail.from.name'))
->text('email.migration.failed_text')
->view('email.migration.failed', [
'special_message' => $special_message,
'logo' => $this->company->present()->logo(),
'settings' => $this->company->settings,
'is_system' => $this->is_system,

View File

@ -51,8 +51,35 @@ class TemplateEmail extends Mailable
$this->invitation = $invitation;
}
/**
* Supports inline attachments for large
* attachments in custom designs
*
* @return string
*/
private function buildLinksForCustomDesign(): string
{
$links = $this->build_email->getAttachmentLinks();
if(count($links) == 0)
return '';
$link_string = '<ul>';
foreach($this->build_email->getAttachmentLinks() as $link)
{
$link_string .= "<li>{$link}</li>";
}
$link_string .= '</ul>';
return $link_string;
}
public function build()
{
$template_name = 'email.template.'.$this->build_email->getTemplate();
if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') {
@ -60,7 +87,7 @@ class TemplateEmail extends Mailable
}
if ($this->build_email->getTemplate() == 'custom') {
$this->build_email->setBody(str_replace('$body', $this->build_email->getBody(), $this->client->getSetting('email_style_custom')));
$this->build_email->setBody(str_replace('$body', $this->build_email->getBody().$this->buildLinksForCustomDesign(), $this->client->getSetting('email_style_custom')));
}
$settings = $this->client->getMergedSettings();

View File

@ -52,6 +52,32 @@ class VendorTemplateEmail extends Mailable
$this->invitation = $invitation;
}
/**
* Supports inline attachments for large
* attachments in custom designs
*
* @return string
*/
private function buildLinksForCustomDesign(): string
{
$links = $this->build_email->getAttachmentLinks();
if(count($links) == 0)
return '';
$link_string = '<ul>';
foreach($this->build_email->getAttachmentLinks() as $link)
{
$link_string .= "<li>{$link}</li>";
}
$link_string .= '</ul>';
return $link_string;
}
public function build()
{
$template_name = 'email.template.'.$this->build_email->getTemplate();
@ -61,7 +87,7 @@ class VendorTemplateEmail extends Mailable
}
if ($this->build_email->getTemplate() == 'custom') {
$this->build_email->setBody(str_replace('$body', $this->build_email->getBody(), $this->company->getSetting('email_style_custom')));
$this->build_email->setBody(str_replace('$body', $this->build_email->getBody().$this->buildLinksForCustomDesign(), $this->company->getSetting('email_style_custom')));
}
$settings = $this->company->settings;

View File

@ -8,6 +8,8 @@
{!! $exception->getMessage() !!}
{!! $content !!}
@else
@if($special_message)
@endif
<p>Please contact us at contact@invoiceninja.com for more information on this error.</p>
@endif
</pre>

View File

@ -172,6 +172,11 @@
<div>
@isset($links)
@if(count($links) >=1)
<p><strong>{{ ctrans('texts.attachments') }}</strong></p>
@endif
@foreach($links as $link)
{!! $link ?? '' !!}<br>
@endforeach

View File

@ -20,6 +20,21 @@
</tr>
@endif
@isset($links)
@if(count($links) >=1)
<p><strong>{{ ctrans('texts.attachments') }}</strong></p>
@endif
@foreach($links as $link)
<tr>
<td>
<p> {!! $link ?? '' !!}</p>
</td>
</tr>
@endforeach
@endisset
@isset($whitelabel)
@if(!$whitelabel)
<p>

View File

@ -40,6 +40,102 @@ class PurchaseOrderTest extends TestCase
$this->makeTestData();
}
public function testPurchaseOrderBulkActions()
{
$i = $this->purchase_order->invitations->first();
$data = [
'ids' =>[$this->purchase_order->hashed_id],
'action' => 'archive',
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post("/api/v1/purchase_orders/bulk", $data)
->assertStatus(200);
$data = [
'ids' =>[$this->purchase_order->hashed_id],
'action' => 'restore',
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post("/api/v1/purchase_orders/bulk", $data)
->assertStatus(200);
$data = [
'ids' =>[$this->purchase_order->hashed_id],
'action' => 'delete',
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post("/api/v1/purchase_orders/bulk", $data)
->assertStatus(200);
$data = [
'ids' =>[$this->purchase_order->hashed_id],
'action' => 'restore',
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post("/api/v1/purchase_orders/bulk", $data)
->assertStatus(200);
$data = [
'ids' =>[$this->purchase_order->hashed_id],
'action' => 'download',
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post("/api/v1/purchase_orders/bulk", $data)
->assertStatus(200);
$data = [
'ids' =>[],
'action' => 'archive',
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post("/api/v1/purchase_orders/bulk", $data)
->assertStatus(302);
$data = [
'ids' =>[$this->purchase_order->hashed_id],
'action' => '',
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post("/api/v1/purchase_orders/bulk", $data)
->assertStatus(302);
$data = [
'ids' =>[$this->purchase_order->hashed_id],
'action' => 'molly',
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post("/api/v1/purchase_orders/bulk", $data)
->assertStatus(302);
}
public function testPurchaseOrderDownloadPDF()
{
$i = $this->purchase_order->invitations->first();