Merge branch 'v5-develop' into v5-stable

This commit is contained in:
David Bomba 2024-02-23 11:42:26 +11:00
commit f2d0d4f172
325 changed files with 331606 additions and 325608 deletions

View File

@ -18,7 +18,7 @@ jobs:
phpunit-versions: ['latest'] phpunit-versions: ['latest']
ci_node_total: [ 8 ] ci_node_total: [ 8 ]
ci_node_index: [ 0, 1, 2, 3, 4, 5, 6, 7] ci_node_index: [ 0, 1, 2, 3, 4, 5, 6, 7]
laravel: [9.*] laravel: [10.*]
dependency-version: [prefer-stable] dependency-version: [prefer-stable]
env: env:

76
.github/workflows/react_release.yml vendored Normal file
View File

@ -0,0 +1,76 @@
on:
release:
types: [released]
name: React Release
jobs:
build:
name: Upload Release Asset
runs-on: ubuntu-latest
steps:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
extensions: mysql, mysqlnd, sqlite3, bcmath, gd, curl, zip, openssl, mbstring, xml
- name: Checkout code
uses: actions/checkout@v1
with:
ref: v5-develop
- name: Copy .env file
run: |
cp .env.example .env
- name: Install composer dependencies
run: |
composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }}
composer install --no-dev
- name: Prepare Laravel Application
run: |
cp .env.example .env
php artisan key:generate --force
php artisan optimize
php artisan storage:link --force
sudo php artisan cache:clear
sudo find ./vendor/bin/ -type f -exec chmod +x {} \;
sudo find ./ -type d -exec chmod 755 {} \;
sudo rm -f public/main.*
sudo rm -f public/flutter*
- name: Prepare React FrontEnd
run: |
git clone https://${{secrets.commit_secret}}@github.com/invoiceninja/ui.git
cd ui
git checkout develop
npm i
npm run build
cp -r dist/* ../public/
- name: Prepare JS/CSS assets
run: |
npm i
npm run production
- name: Cleanup Builds
run: |
sudo rm -rf bootstrap/cache/*
sudo rm -rf node_modules
sudo rm -rf .git
sudo rm .env
- name: Build project
run: |
shopt -s dotglob
tar --exclude='public/storage' --exclude='.htaccess' --exclude='invoiceninja.zip' -zcvf /home/runner/work/invoiceninja/react-invoiceninja.tar *
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: |
/home/runner/work/invoiceninja/react-invoiceninja.tar

View File

@ -1 +1 @@
5.8.21 5.8.28

View File

@ -891,7 +891,7 @@ class CheckData extends Command
$this->logMessage("Fixing country for # {$client->id}"); $this->logMessage("Fixing country for # {$client->id}");
}); });
Client::query()->whereNull("settings->currency_id")->cursor()->each(function ($client){ Client::query()->whereNull("settings->currency_id")->cursor()->each(function ($client) {
$settings = $client->settings; $settings = $client->settings;
$settings->currency_id = (string)$client->company->settings->currency_id; $settings->currency_id = (string)$client->company->settings->currency_id;
$client->settings = $settings; $client->settings = $settings;
@ -901,7 +901,7 @@ class CheckData extends Command
}); });
Payment::withTrashed()->where('exchange_rate', 0)->cursor()->each(function ($payment){ Payment::withTrashed()->where('exchange_rate', 0)->cursor()->each(function ($payment) {
$payment->exchange_rate = 1; $payment->exchange_rate = 1;
$payment->saveQuietly(); $payment->saveQuietly();
@ -917,11 +917,11 @@ class CheckData extends Command
$p->currency_id = $p->client->settings->currency_id; $p->currency_id = $p->client->settings->currency_id;
$p->saveQuietly(); $p->saveQuietly();
$this->logMessage("Fixing currency for # {$p->id}"); $this->logMessage("Fixing currency for # {$p->id}");
}); });
Company::whereNull("subdomain") Company::whereNull("subdomain")
->cursor() ->cursor()
->when(Ninja::isHosted()) ->when(Ninja::isHosted())
@ -942,7 +942,7 @@ class CheckData extends Command
$i->partial_due_date = null; $i->partial_due_date = null;
$i->saveQuietly(); $i->saveQuietly();
$this->logMessage("Fixing partial due date for # {$i->id}"); $this->logMessage("Fixing partial due date for # {$i->id}");
}); });

View File

@ -0,0 +1,84 @@
<?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\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class EncryptNinja extends Command
{
protected $files = [
'resources/views/email/template/admin_premium.blade.php',
'resources/views/email/template/client_premium.blade.php',
];
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ninja:crypt {--encrypt} {--decrypt}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Encrypt Protected files';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
if($this->option('encrypt')) {
return $this->encryptFiles();
}
if($this->option('decrypt')) {
return $this->decryptFiles();
}
}
private function encryptFiles()
{
foreach ($this->files as $file) {
$contents = Storage::disk('base')->get($file);
$encrypted = encrypt($contents);
Storage::disk('base')->put($file.".enc", $encrypted);
// Storage::disk('base')->delete($file);
}
}
private function decryptFiles()
{
foreach ($this->files as $file) {
$encrypted_file = "{$file}.enc";
$contents = Storage::disk('base')->get($encrypted_file);
$decrypted = decrypt($contents);
Storage::disk('base')->put($file, $decrypted);
}
}
}

View File

@ -78,9 +78,10 @@ class OpenApiYaml extends Command
Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents($path.'/components.yaml')); Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents($path.'/components.yaml'));
Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents($path.'/components/responses.yaml'));
Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents($path.'/components/examples.yaml')); Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents($path.'/components/examples.yaml'));
Storage::disk('base')->append('/openapi/api-docs.yaml', file_get_contents($path.'/components/responses.yaml'));
$directory = new DirectoryIterator($path . '/components/responses/'); $directory = new DirectoryIterator($path . '/components/responses/');
foreach ($directory as $file) { foreach ($directory as $file) {

View File

@ -494,8 +494,11 @@ class CompanySettings extends BaseSettings
public $payment_email_all_contacts = false; public $payment_email_all_contacts = false;
public $show_pdfhtml_on_mobile = true; public $show_pdfhtml_on_mobile = true;
public $use_unapplied_payment = 'off'; //always, option, off //@implemented
public static $casts = [ public static $casts = [
'use_unapplied_payment' => 'string',
'show_pdfhtml_on_mobile' => 'bool', 'show_pdfhtml_on_mobile' => 'bool',
'payment_email_all_contacts' => 'bool', 'payment_email_all_contacts' => 'bool',
'statement_design_id' => 'string', 'statement_design_id' => 'string',
@ -878,7 +881,7 @@ class CompanySettings extends BaseSettings
{ {
$notification = new stdClass(); $notification = new stdClass();
$notification->email = []; $notification->email = [];
$notification->email = ['invoice_sent_all']; $notification->email = ['invoice_sent_all','payment_success_all','payment_manual_all'];
return $notification; return $notification;
} }

View File

@ -59391,7 +59391,7 @@ class Domains
'wireconnected.com' 'wireconnected.com'
]; ];
public static function getDomains() public static function getDomains(): array
{ {
return self::$verify_domains; return self::$verify_domains;
} }

View File

@ -104,10 +104,10 @@ class Handler extends ExceptionHandler
if (Ninja::isHosted()) { if (Ninja::isHosted()) {
if($exception instanceof ThrottleRequestsException && class_exists(\Modules\Admin\Events\ThrottledExceptionRaised::class)) { // if($exception instanceof ThrottleRequestsException && class_exists(\Modules\Admin\Events\ThrottledExceptionRaised::class)) {
$uri = urldecode(request()->getRequestUri()); // $uri = urldecode(request()->getRequestUri());
// event(new \Modules\Admin\Events\ThrottledExceptionRaised(auth()->user()?->account?->key, $uri, request()->ip())); // event(new \Modules\Admin\Events\ThrottledExceptionRaised(auth()->user()?->account?->key, $uri, request()->ip()));
} // }
Integration::configureScope(function (Scope $scope): void { Integration::configureScope(function (Scope $scope): void {
$name = 'hosted@invoiceninja.com'; $name = 'hosted@invoiceninja.com';

View File

@ -450,9 +450,20 @@ class BaseExport
protected function filterByClients($query) protected function filterByClients($query)
{ {
if (isset($this->input['client_id']) && $this->input['client_id'] != 'all') { if (isset($this->input['client_id']) && $this->input['client_id'] != 'all') {
if(!is_int($this->input['client_id'])) {
$this->input['client_id'] = $this->decodePrimaryKey($this->input['client_id']);
}
$client = Client::withTrashed()->find($this->input['client_id']); $client = Client::withTrashed()->find($this->input['client_id']);
if(!$client) {
return $query;
}
$this->client_description = $client->present()->name; $this->client_description = $client->present()->name;
return $query->where('client_id', $this->input['client_id']); return $query->where('client_id', $this->input['client_id']);
} elseif(isset($this->input['clients']) && count($this->input['clients']) > 0) { } elseif(isset($this->input['clients']) && count($this->input['clients']) > 0) {
$this->client_description = 'Multiple Clients'; $this->client_description = 'Multiple Clients';
@ -835,38 +846,69 @@ class BaseExport
} }
protected function addClientFilter($query, $clients): Builder protected function addClientFilter($query, $clients): Builder
{ {
$transformed_clients = $this->transformKeys(explode(',', $clients)); if(is_string($clients)) {
$clients = explode(',', $clients);
$query->whereIn('client_id', $transformed_clients); }
$transformed_clients = $this->transformKeys($clients);
nlog($clients);
nlog($transformed_clients);
if(count($transformed_clients) > 0) {
$query->whereIn('client_id', $transformed_clients);
}
return $query; return $query;
} }
protected function addVendorFilter($query, $vendors): Builder protected function addVendorFilter($query, $vendors): Builder
{ {
$transformed_vendors = $this->transformKeys(explode(',', $vendors));
if(is_string($vendors)) {
$query->whereIn('vendor_id', $transformed_vendors); $vendors = explode(',', $vendors);
}
$transformed_vendors = $this->transformKeys($vendors);
if(count($transformed_vendors) > 0) {
$query->whereIn('vendor_id', $transformed_vendors);
}
return $query; return $query;
} }
protected function addProjectFilter($query, $projects): Builder protected function addProjectFilter($query, $projects): Builder
{ {
$transformed_projects = $this->transformKeys(explode(',', $projects));
if(is_string($projects)) {
$query->whereIn('project_id', $transformed_projects); $projects = explode(',', $projects);
}
$transformed_projects = $this->transformKeys($projects);
if(count($transformed_projects) > 0) {
$query->whereIn('project_id', $transformed_projects);
}
return $query; return $query;
} }
protected function addCategoryFilter($query, $expense_categories): Builder protected function addCategoryFilter($query, $expense_categories): Builder
{ {
$transformed_expense_categories = $this->transformKeys(explode(',', $expense_categories));
if(is_string($expense_categories)) {
$query->whereIn('category_id', $transformed_expense_categories); $expense_categories = explode(',', $expense_categories);
}
$transformed_expense_categories = $this->transformKeys($expense_categories);
if(count($transformed_expense_categories) > 0) {
$query->whereIn('category_id', $transformed_expense_categories);
}
return $query; return $query;
} }
@ -875,7 +917,6 @@ class BaseExport
$status_parameters = explode(',', $status); $status_parameters = explode(',', $status);
if(in_array('all', $status_parameters)) { if(in_array('all', $status_parameters)) {
return $query; return $query;
} }
@ -931,6 +972,8 @@ class BaseExport
$date_range = $this->input['date_range']; $date_range = $this->input['date_range'];
nlog($date_range);
if (array_key_exists('date_key', $this->input) && strlen($this->input['date_key']) > 1) { if (array_key_exists('date_key', $this->input) && strlen($this->input['date_key']) > 1) {
$this->date_key = $this->input['date_key']; $this->date_key = $this->input['date_key'];
} }
@ -1262,27 +1305,30 @@ class BaseExport
public function queueDocuments(Builder $query) public function queueDocuments(Builder $query)
{ {
nlog("queue docs pls");
if($query->getModel() instanceof Document) if($query->getModel() instanceof Document) {
$documents = $query->pluck('id')->toArray(); $documents = $query->pluck('id')->toArray();
else{ } else {
$documents = $query->cursor() $documents = $query->cursor()
->map(function ($entity){ ->map(function ($entity) {
return $entity->documents()->pluck('id')->toArray(); return $entity->documents()->pluck('id')->toArray();
})->flatten() })->flatten()
->toArray(); ->toArray();
} }
nlog($documents); nlog($documents);
if(count($documents) > 0) { if(count($documents) > 0) {
$user = $this->company->owner(); $user = $this->company->owner();
if(auth()->user() && auth()->user()->account_id == $this->company->account_id) if(auth()->user() && auth()->user()->account_id == $this->company->account_id) {
$user = auth()->user(); $user = auth()->user();
}
if($this->input['user_id']) if($this->input['user_id'] ?? false) {
$user = User::where('id', $this->input['user_id'])->where('account_id', $this->company->account_id)->first(); $user = User::where('id', $this->input['user_id'])->where('account_id', $this->company->account_id)->first();
}
ZipDocuments::dispatch($documents, $this->company, $user); ZipDocuments::dispatch($documents, $this->company, $user);
} }

View File

@ -225,22 +225,6 @@ class ClientExport extends BaseExport
$entity['client.assigned_user'] = $client->assigned_user ? $client->user->present()->name() : ''; $entity['client.assigned_user'] = $client->assigned_user ? $client->user->present()->name() : '';
} }
// if (in_array('client.country_id', $this->input['report_keys'])) {
// $entity['client.country_id'] = $client->country ? ctrans("texts.country_{$client->country->name}") : '';
// }
// if (in_array('client.shipping_country_id', $this->input['report_keys'])) {
// $entity['client.shipping_country_id'] = $client->shipping_country ? ctrans("texts.country_{$client->shipping_country->name}") : '';
// }
// if (in_array('client.currency_id', $this->input['report_keys'])) {
// $entity['client.currency_id'] = $client->currency() ? $client->currency()->code : $client->company->currency()->code;
// }
// if (in_array('client.industry_id', $this->input['report_keys'])) {
// $entity['industry_id'] = $client->industry ? ctrans("texts.industry_{$client->industry->name}") : '';
// }
if (in_array('client.classification', $this->input['report_keys']) && isset($client->classification)) { if (in_array('client.classification', $this->input['report_keys']) && isset($client->classification)) {
$entity['client.classification'] = ctrans("texts.{$client->classification}") ?? ''; $entity['client.classification'] = ctrans("texts.{$client->classification}") ?? '';
} }

View File

@ -106,7 +106,7 @@ class CreditExport extends BaseExport
->where('is_deleted', 0); ->where('is_deleted', 0);
$query = $this->addDateRange($query); $query = $this->addDateRange($query);
if($this->input['document_email_attachment'] ?? false) { if($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query); $this->queueDocuments($query);
} }

View File

@ -77,7 +77,7 @@ class DocumentExport extends BaseExport
$query = Document::query()->where('company_id', $this->company->id); $query = Document::query()->where('company_id', $this->company->id);
$query = $this->addDateRange($query); $query = $this->addDateRange($query);
if($this->input['document_email_attachment'] ?? false) { if($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query); $this->queueDocuments($query);
} }

View File

@ -23,7 +23,6 @@ use League\Csv\Writer;
class ExpenseExport extends BaseExport class ExpenseExport extends BaseExport
{ {
private $expense_transformer; private $expense_transformer;
private Decorator $decorator; private Decorator $decorator;
@ -103,7 +102,7 @@ class ExpenseExport extends BaseExport
if(isset($this->input['categories'])) { if(isset($this->input['categories'])) {
$query = $this->addCategoryFilter($query, $this->input['categories']); $query = $this->addCategoryFilter($query, $this->input['categories']);
} }
if($this->input['document_email_attachment'] ?? false) { if($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query); $this->queueDocuments($query);
} }
@ -206,23 +205,21 @@ class ExpenseExport extends BaseExport
if($expense->calculate_tax_by_amount) { if($expense->calculate_tax_by_amount) {
$total_tax_amount = round($expense->tax_amount1 + $expense->tax_amount2 + $expense->tax_amount3, $precision); $total_tax_amount = round($expense->tax_amount1 + $expense->tax_amount2 + $expense->tax_amount3, $precision);
} } else {
else {
if($expense->uses_inclusive_taxes) {
if($expense->uses_inclusive_taxes){ $total_tax_amount = ($this->calcInclusiveLineTax($expense->tax_rate1 ?? 0, $expense->amount, $precision)) + ($this->calcInclusiveLineTax($expense->tax_rate2 ?? 0, $expense->amount, $precision)) + ($this->calcInclusiveLineTax($expense->tax_rate3 ?? 0, $expense->amount, $precision));
$total_tax_amount = ($this->calcInclusiveLineTax($expense->tax_rate1 ?? 0, $expense->amount,$precision)) + ($this->calcInclusiveLineTax($expense->tax_rate2 ?? 0, $expense->amount,$precision)) + ($this->calcInclusiveLineTax($expense->tax_rate3 ?? 0, $expense->amount,$precision));
$entity['expense.net_amount'] = round(($expense->amount - round($total_tax_amount, $precision)), $precision); $entity['expense.net_amount'] = round(($expense->amount - round($total_tax_amount, $precision)), $precision);
} } else {
else{ $total_tax_amount = ($expense->amount * (($expense->tax_rate1 ?? 0) / 100)) + ($expense->amount * (($expense->tax_rate2 ?? 0) / 100)) + ($expense->amount * (($expense->tax_rate3 ?? 0) / 100));
$total_tax_amount = ($expense->amount * (($expense->tax_rate1 ?? 0)/100)) + ($expense->amount * (($expense->tax_rate2 ?? 0)/100)) + ($expense->amount * (($expense->tax_rate3 ?? 0)/100));
$entity['expense.net_amount'] = round(($expense->amount + round($total_tax_amount, $precision)), $precision); $entity['expense.net_amount'] = round(($expense->amount + round($total_tax_amount, $precision)), $precision);
} }
} }
$entity['expense.tax_amount'] = round($total_tax_amount, $precision); $entity['expense.tax_amount'] = round($total_tax_amount, $precision);
return $entity; return $entity;
} }
private function calcInclusiveLineTax($tax_rate, $amount, $precision): float private function calcInclusiveLineTax($tax_rate, $amount, $precision): float

View File

@ -76,7 +76,7 @@ class InvoiceItemExport extends BaseExport
$query = $this->addDateRange($query); $query = $this->addDateRange($query);
$query = $this->applyFilters($query); $query = $this->applyFilters($query);
if($this->input['document_email_attachment'] ?? false) { if($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query); $this->queueDocuments($query);
} }

View File

@ -60,7 +60,7 @@ class PaymentExport extends BaseExport
->where('is_deleted', 0); ->where('is_deleted', 0);
$query = $this->addDateRange($query); $query = $this->addDateRange($query);
if($this->input['document_email_attachment'] ?? false) { if($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query); $this->queueDocuments($query);
} }

View File

@ -77,7 +77,7 @@ class ProductExport extends BaseExport
->where('is_deleted', 0); ->where('is_deleted', 0);
$query = $this->addDateRange($query); $query = $this->addDateRange($query);
if($this->input['document_email_attachment'] ?? false) { if($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query); $this->queueDocuments($query);
} }

View File

@ -107,7 +107,7 @@ class PurchaseOrderExport extends BaseExport
->where('is_deleted', 0); ->where('is_deleted', 0);
$query = $this->addDateRange($query); $query = $this->addDateRange($query);
if($this->input['document_email_attachment'] ?? false) { if($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query); $this->queueDocuments($query);
} }

View File

@ -69,7 +69,7 @@ class QuoteItemExport extends BaseExport
->where('is_deleted', 0); ->where('is_deleted', 0);
$query = $this->addDateRange($query); $query = $this->addDateRange($query);
if($this->input['document_email_attachment'] ?? false) { if($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query); $this->queueDocuments($query);
} }

View File

@ -29,7 +29,7 @@ class ClientDecorator extends Decorator implements DecoratorInterface
if($client && method_exists($this, $key)) { if($client && method_exists($this, $key)) {
return $this->{$key}($client); return $this->{$key}($client);
} elseif($client && $client->{$key}) { } elseif($client && ($client->{$key} ?? false)) {
return $client->{$key}; return $client->{$key};
} }

View File

@ -27,7 +27,7 @@ class ContactDecorator implements DecoratorInterface
if($contact && method_exists($this, $key)) { if($contact && method_exists($this, $key)) {
return $this->{$key}($contact); return $this->{$key}($contact);
} elseif($contact && $contact->{$key}) { } elseif($contact && ($contact->{$key} ?? false)) {
return $contact->{$key}; return $contact->{$key};
} }

View File

@ -27,7 +27,7 @@ class CreditDecorator implements DecoratorInterface
if($credit && method_exists($this, $key)) { if($credit && method_exists($this, $key)) {
return $this->{$key}($credit); return $this->{$key}($credit);
} elseif($credit && $credit->{$key}) { } elseif($credit && ($credit->{$key} ?? false)) {
return $credit->{$key}; return $credit->{$key};
} }

View File

@ -27,7 +27,7 @@ class ExpenseDecorator implements DecoratorInterface
if($expense && method_exists($this, $key)) { if($expense && method_exists($this, $key)) {
return $this->{$key}($expense); return $this->{$key}($expense);
} elseif($expense && $expense->{$key}) { } elseif($expense && ($expense->{$key} ?? false)) {
return $expense->{$key}; return $expense->{$key};
} }
@ -35,6 +35,11 @@ class ExpenseDecorator implements DecoratorInterface
} }
public function category(Expense $expense)
{
return $this->category_id($expense);
}
public function category_id(Expense $expense) public function category_id(Expense $expense)
{ {
return $expense->category ? $expense->category->name : ''; return $expense->category ? $expense->category->name : '';

View File

@ -29,7 +29,7 @@ class InvoiceDecorator extends Decorator implements DecoratorInterface
if($invoice && method_exists($this, $key)) { if($invoice && method_exists($this, $key)) {
return $this->{$key}($invoice); return $this->{$key}($invoice);
} elseif($invoice && $invoice->{$key}) { } elseif($invoice && ($invoice->{$key} ?? false)) {
return $invoice->{$key}; return $invoice->{$key};
} }

View File

@ -41,7 +41,7 @@ class PaymentDecorator extends Decorator implements DecoratorInterface
if($payment && method_exists($this, $key)) { if($payment && method_exists($this, $key)) {
return $this->{$key}($payment); return $this->{$key}($payment);
} elseif($payment && $payment->{$key}) { } elseif($payment && ($payment->{$key} ?? false)) {
return $payment->{$key}; return $payment->{$key};
} }

View File

@ -27,7 +27,7 @@ class ProductDecorator implements DecoratorInterface
if($product && method_exists($this, $key)) { if($product && method_exists($this, $key)) {
return $this->{$key}($product); return $this->{$key}($product);
} elseif($product->{$key}) { } elseif($product->{$key} ?? false) {
return $product->{$key} ?? ''; return $product->{$key} ?? '';
} }

View File

@ -27,7 +27,7 @@ class PurchaseOrderDecorator extends Decorator implements DecoratorInterface
if($purchase_order && method_exists($this, $key)) { if($purchase_order && method_exists($this, $key)) {
return $this->{$key}($purchase_order); return $this->{$key}($purchase_order);
} elseif($purchase_order->{$key}) { } elseif($purchase_order->{$key} ?? false) {
return $purchase_order->{$key} ?? ''; return $purchase_order->{$key} ?? '';
} }

View File

@ -27,7 +27,7 @@ class QuoteDecorator extends Decorator implements DecoratorInterface
if($quote && method_exists($this, $key)) { if($quote && method_exists($this, $key)) {
return $this->{$key}($quote); return $this->{$key}($quote);
} elseif($quote->{$key}) { } elseif($quote->{$key} ?? false) {
return $quote->{$key} ?? ''; return $quote->{$key} ?? '';
} }

View File

@ -27,7 +27,7 @@ class RecurringInvoiceDecorator extends Decorator implements DecoratorInterface
if($recurring_invoice && method_exists($this, $key)) { if($recurring_invoice && method_exists($this, $key)) {
return $this->{$key}($recurring_invoice); return $this->{$key}($recurring_invoice);
} elseif($recurring_invoice->{$key}) { } elseif($recurring_invoice->{$key} ?? false) {
return $recurring_invoice->{$key} ?? ''; return $recurring_invoice->{$key} ?? '';
} }

View File

@ -30,7 +30,7 @@ class TaskDecorator extends Decorator implements DecoratorInterface
if($task && method_exists($this, $key)) { if($task && method_exists($this, $key)) {
return $this->{$key}($task); return $this->{$key}($task);
} elseif($task && $task->{$key}) { } elseif($task && $task->{$key} ?? false) {
return $task->{$key}; return $task->{$key};
} }

View File

@ -27,7 +27,7 @@ class VendorContactDecorator implements DecoratorInterface
if($contact && method_exists($this, $key)) { if($contact && method_exists($this, $key)) {
return $this->{$key}($contact); return $this->{$key}($contact);
} elseif($contact && $contact->{$key}) { } elseif($contact && ($contact->{$key} ?? false)) {
return $contact->{$key} ?? ''; return $contact->{$key} ?? '';
} }

View File

@ -27,7 +27,7 @@ class VendorDecorator extends Decorator implements DecoratorInterface
if($vendor && method_exists($this, $key)) { if($vendor && method_exists($this, $key)) {
return $this->{$key}($vendor); return $this->{$key}($vendor);
} elseif($vendor->{$key}) { } elseif($vendor->{$key} ?? false) {
return $vendor->{$key} ?? ''; return $vendor->{$key} ?? '';
} }

View File

@ -49,6 +49,14 @@ class CompanyFactory
$company->markdown_enabled = false; $company->markdown_enabled = false;
$company->tax_data = new TaxModel(); $company->tax_data = new TaxModel();
$company->first_month_of_year = 1; $company->first_month_of_year = 1;
$company->smtp_encryption = 'tls';
$company->smtp_host = '';
$company->smtp_local_domain = '';
$company->smtp_password = '';
$company->smtp_port = '';
$company->smtp_username = '';
$company->smtp_verify_peer = true;
return $company; return $company;
} }
} }

View File

@ -165,6 +165,10 @@ class ClientFilters extends QueryFilters
$dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc'; $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
if($sort_col[0] == 'number') {
return $this->builder->orderByRaw('ABS(number) ' . $dir);
}
return $this->builder->orderBy($sort_col[0], $dir); return $this->builder->orderBy($sort_col[0], $dir);
} }

View File

@ -146,6 +146,11 @@ class CreditFilters extends QueryFilters
->whereColumn('clients.id', 'credits.client_id'), $dir); ->whereColumn('clients.id', 'credits.client_id'), $dir);
} }
if($sort_col[0] == 'number') {
return $this->builder->orderByRaw('ABS(number) ' . $dir);
}
return $this->builder->orderBy($sort_col[0], $dir); return $this->builder->orderBy($sort_col[0], $dir);
} }

View File

@ -172,6 +172,8 @@ class ExpenseFilters extends QueryFilters
return $this->builder; return $this->builder;
} }
$dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
if ($sort_col[0] == 'client_id' && in_array($sort_col[1], ['asc', 'desc'])) { if ($sort_col[0] == 'client_id' && in_array($sort_col[1], ['asc', 'desc'])) {
return $this->builder return $this->builder
->orderByRaw('ISNULL(client_id), client_id '. $sort_col[1]) ->orderByRaw('ISNULL(client_id), client_id '. $sort_col[1])
@ -194,6 +196,10 @@ class ExpenseFilters extends QueryFilters
->whereColumn('expense_categories.id', 'expenses.category_id'), $sort_col[1]); ->whereColumn('expense_categories.id', 'expenses.category_id'), $sort_col[1]);
} }
if($sort_col[0] == 'number') {
return $this->builder->orderByRaw('ABS(number) ' . $dir);
}
if (is_array($sort_col) && in_array($sort_col[1], ['asc', 'desc']) && in_array($sort_col[0], ['public_notes', 'date', 'id_number', 'custom_value1', 'custom_value2', 'custom_value3', 'custom_value4'])) { if (is_array($sort_col) && in_array($sort_col[1], ['asc', 'desc']) && in_array($sort_col[0], ['public_notes', 'date', 'id_number', 'custom_value1', 'custom_value2', 'custom_value3', 'custom_value4'])) {
return $this->builder->orderBy($sort_col[0], $sort_col[1]); return $this->builder->orderBy($sort_col[0], $sort_col[1]);
} }

View File

@ -323,6 +323,10 @@ class InvoiceFilters extends QueryFilters
} }
if($sort_col[0] == 'number') {
return $this->builder->orderByRaw('ABS(number) ' . $dir);
}
return $this->builder->orderBy($sort_col[0], $dir); return $this->builder->orderBy($sort_col[0], $dir);
} }

View File

@ -12,8 +12,9 @@
namespace App\Filters; namespace App\Filters;
use App\Models\Payment; use App\Models\Payment;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Eloquent\Builder;
/** /**
* PaymentFilters. * PaymentFilters.
@ -163,18 +164,22 @@ class PaymentFilters extends QueryFilters
{ {
$sort_col = explode('|', $sort); $sort_col = explode('|', $sort);
if (!is_array($sort_col) || count($sort_col) != 2) { if (!is_array($sort_col) || count($sort_col) != 2 || !in_array($sort_col, Schema::getColumnListing('payments'))) {
return $this->builder; return $this->builder;
} }
$dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
if ($sort_col[0] == 'client_id') { if ($sort_col[0] == 'client_id') {
return $this->builder->orderBy(\App\Models\Client::select('name') return $this->builder->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'payments.client_id'), $sort_col[1]); ->whereColumn('clients.id', 'payments.client_id'), $dir);
} }
if($sort_col[0] == 'number') {
return $this->builder->orderByRaw('ABS(number) ' . $dir);
}
return $this->builder->orderBy($sort_col[0], $sort_col[1]); return $this->builder->orderBy($sort_col[0], $dir);
} }
public function date_range(string $date_range = ''): Builder public function date_range(string $date_range = ''): Builder

View File

@ -60,20 +60,23 @@ class ProjectFilters extends QueryFilters
{ {
$sort_col = explode('|', $sort); $sort_col = explode('|', $sort);
if ($sort_col[0] == 'client_id') {
return $this->builder->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'projects.client_id'), $sort_col[1]);
}
if (!is_array($sort_col) || count($sort_col) != 2) { if (!is_array($sort_col) || count($sort_col) != 2) {
return $this->builder; return $this->builder;
} }
if (is_array($sort_col) && in_array($sort_col[1], ['asc','desc'])) { $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
if ($sort_col[0] == 'client_id') {
return $this->builder->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'projects.client_id'), $dir);
} }
return $this->builder; if($sort_col[0] == 'number') {
return $this->builder->orderByRaw('ABS(number) ' . $dir);
}
return $this->builder->orderBy($sort_col[0], $dir);
} }
/** /**

View File

@ -130,6 +130,10 @@ class PurchaseOrderFilters extends QueryFilters
->whereColumn('vendors.id', 'purchase_orders.vendor_id'), $dir); ->whereColumn('vendors.id', 'purchase_orders.vendor_id'), $dir);
} }
if($sort_col[0] == 'number') {
return $this->builder->orderByRaw('ABS(number) ' . $dir);
}
return $this->builder->orderBy($sort_col[0], $dir); return $this->builder->orderBy($sort_col[0], $dir);
} }

View File

@ -155,6 +155,10 @@ class QuoteFilters extends QueryFilters
} }
if($sort_col[0] == 'number') {
return $this->builder->orderByRaw('ABS(number) ' . $dir);
}
if ($sort_col[0] == 'valid_until') { if ($sort_col[0] == 'valid_until') {
$sort_col[0] = 'due_date'; $sort_col[0] = 'due_date';
} }

View File

@ -31,13 +31,22 @@ class RecurringExpenseFilters extends QueryFilters
return $this->builder; return $this->builder;
} }
return $this->builder->where(function ($query) use ($filter) { return $this->builder->where(function ($query) use ($filter) {
$query->where('public_notes', 'like', '%'.$filter.'%') $query->where('number', 'like', '%' . $filter . '%')
->orWhere('custom_value1', 'like', '%'.$filter.'%') ->orWhere('amount', 'like', '%' . $filter . '%')
->orWhere('custom_value2', 'like', '%'.$filter.'%') ->orWhere('public_notes', 'like', '%' . $filter . '%')
->orWhere('custom_value3', 'like', '%'.$filter.'%') ->orWhere('custom_value1', 'like', '%' . $filter . '%')
->orWhere('custom_value4', 'like', '%'.$filter.'%'); ->orWhere('custom_value2', 'like', '%' . $filter . '%')
->orWhere('custom_value3', 'like', '%' . $filter . '%')
->orWhere('custom_value4', 'like', '%' . $filter . '%')
->orWhereHas('category', function ($q) use ($filter) {
$q->where('name', 'like', '%' . $filter . '%');
})
->orWhereHas('vendor', function ($q) use ($filter) {
$q->where('name', 'like', '%' . $filter . '%');
});
}); });
} }
public function number(string $number = ''): Builder public function number(string $number = ''): Builder
@ -49,6 +58,74 @@ class RecurringExpenseFilters extends QueryFilters
return $this->builder->where('number', $number); return $this->builder->where('number', $number);
} }
/**
* Filter based on client status.
*
* Statuses we need to handle
* - all
* - logged
* - pending
* - invoiced
* - paid
* - unpaid
*
* @return Builder
*/
public function client_status(string $value = ''): Builder
{
if (strlen($value) == 0) {
return $this->builder;
}
$status_parameters = explode(',', $value);
if (in_array('all', $status_parameters)) {
return $this->builder;
}
$this->builder->where(function ($query) use ($status_parameters) {
if (in_array('logged', $status_parameters)) {
$query->orWhere(function ($query) {
$query->where('amount', '>', 0)
->whereNull('invoice_id')
->whereNull('payment_date')
->where('should_be_invoiced', false);
});
}
if (in_array('pending', $status_parameters)) {
$query->orWhere(function ($query) {
$query->where('should_be_invoiced', true)
->whereNull('invoice_id');
});
}
if (in_array('invoiced', $status_parameters)) {
$query->orWhere(function ($query) {
$query->whereNotNull('invoice_id');
});
}
if (in_array('paid', $status_parameters)) {
$query->orWhere(function ($query) {
$query->whereNotNull('payment_date');
});
}
if (in_array('unpaid', $status_parameters)) {
$query->orWhere(function ($query) {
$query->whereNull('payment_date');
});
}
});
// nlog($this->builder->toSql());
return $this->builder;
}
/** /**
* Sorts the list based on $sort. * Sorts the list based on $sort.
* *
@ -65,7 +142,37 @@ class RecurringExpenseFilters extends QueryFilters
$dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc'; $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
return $this->builder->orderBy($sort_col[0], $dir); if ($sort_col[0] == 'client_id' && in_array($sort_col[1], ['asc', 'desc'])) {
return $this->builder
->orderByRaw('ISNULL(client_id), client_id '. $sort_col[1])
->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'expenses.client_id'), $sort_col[1]);
}
if ($sort_col[0] == 'vendor_id' && in_array($sort_col[1], ['asc', 'desc'])) {
return $this->builder
->orderByRaw('ISNULL(vendor_id), vendor_id '. $sort_col[1])
->orderBy(\App\Models\Vendor::select('name')
->whereColumn('vendors.id', 'expenses.vendor_id'), $sort_col[1]);
}
if ($sort_col[0] == 'category_id' && in_array($sort_col[1], ['asc', 'desc'])) {
return $this->builder
->orderByRaw('ISNULL(category_id), category_id '. $sort_col[1])
->orderBy(\App\Models\ExpenseCategory::select('name')
->whereColumn('expense_categories.id', 'expenses.category_id'), $sort_col[1]);
}
if($sort_col[0] == 'number') {
return $this->builder->orderByRaw('ABS(number) ' . $dir);
}
if (is_array($sort_col) && in_array($sort_col[1], ['asc', 'desc']) && in_array($sort_col[0], ['public_notes', 'date', 'id_number', 'custom_value1', 'custom_value2', 'custom_value3', 'custom_value4'])) {
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
}
return $this->builder;
} }
/** /**

View File

@ -143,6 +143,10 @@ class TaskFilters extends QueryFilters
->whereColumn('users.id', 'tasks.user_id'), $dir); ->whereColumn('users.id', 'tasks.user_id'), $dir);
} }
if($sort_col[0] == 'number') {
return $this->builder->orderByRaw('ABS(number) ' . $dir);
}
return $this->builder->orderBy($sort_col[0], $dir); return $this->builder->orderBy($sort_col[0], $dir);
} }

View File

@ -50,7 +50,7 @@ class UserFilters extends QueryFilters
{ {
$sort_col = explode('|', $sort); $sort_col = explode('|', $sort);
if (!is_array($sort_col) || count($sort_col) != 2) { if (!is_array($sort_col) || count($sort_col) != 2 || !in_array($sort_col, \Illuminate\Support\Facades\Schema::getColumnListing('users'))) {
return $this->builder; return $this->builder;
} }

View File

@ -71,6 +71,10 @@ class VendorFilters extends QueryFilters
$dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc'; $dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
if($sort_col[0] == 'number') {
return $this->builder->orderByRaw('ABS(number) ' . $dir);
}
return $this->builder->orderBy($sort_col[0], $dir); return $this->builder->orderBy($sort_col[0], $dir);
} }

View File

@ -19,6 +19,7 @@
namespace App\Helpers\Bank\Nordigen; namespace App\Helpers\Bank\Nordigen;
use App\Models\Company;
use App\Services\Email\Email; use App\Services\Email\Email;
use App\Models\BankIntegration; use App\Models\BankIntegration;
use App\Services\Email\EmailObject; use App\Services\Email\EmailObject;
@ -138,11 +139,11 @@ class Nordigen
* @param string $dateFrom * @param string $dateFrom
* @return array * @return array
*/ */
public function getTransactions(string $accountId, string $dateFrom = null): array public function getTransactions(Company $company, string $accountId, string $dateFrom = null): array
{ {
$transactionResponse = $this->client->account($accountId)->getAccountTransactions($dateFrom); $transactionResponse = $this->client->account($accountId)->getAccountTransactions($dateFrom);
$it = new TransactionTransformer(); $it = new TransactionTransformer($company);
return $it->transform($transactionResponse); return $it->transform($transactionResponse);
} }

View File

@ -104,9 +104,9 @@ class AccountTransformer implements AccountTransformerInterface
return [ return [
'id' => $nordigen_account->metadata["id"], 'id' => $nordigen_account->metadata["id"],
'account_type' => "bank", 'account_type' => "bank",
'account_name' => $nordigen_account->data["iban"], 'account_name' => isset($nordigen_account->data["iban"]) ? $nordigen_account->data["iban"] : '',
'account_status' => $nordigen_account->metadata["status"], 'account_status' => $nordigen_account->metadata["status"],
'account_number' => '**** ' . substr($nordigen_account->data["iban"], -7), 'account_number' => isset($nordigen_account->data["iban"]) ? '**** ' . substr($nordigen_account->data["iban"], -7) : '',
'provider_account_id' => $nordigen_account->metadata["id"], 'provider_account_id' => $nordigen_account->metadata["id"],
'provider_id' => $nordigen_account->institution["id"], 'provider_id' => $nordigen_account->institution["id"],
'provider_name' => $nordigen_account->institution["name"], 'provider_name' => $nordigen_account->institution["name"],

View File

@ -12,7 +12,10 @@
namespace App\Helpers\Bank\Nordigen\Transformer; namespace App\Helpers\Bank\Nordigen\Transformer;
use App\Helpers\Bank\BankRevenueInterface; use App\Helpers\Bank\BankRevenueInterface;
use App\Models\BankIntegration; use App\Models\Company;
use App\Models\DateFormat;
use App\Models\Timezone;
use Carbon\Carbon;
use App\Utils\Traits\AppSetup; use App\Utils\Traits\AppSetup;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Log; use Log;
@ -66,12 +69,20 @@ class TransactionTransformer implements BankRevenueInterface
{ {
use AppSetup; use AppSetup;
private Company $company;
function __construct(Company $company)
{
$this->company = $company;
}
public function transform($transactionResponse) public function transform($transactionResponse)
{ {
$data = []; $data = [];
if (!array_key_exists('transactions', $transactionResponse) || !array_key_exists('booked', $transactionResponse["transactions"])) if (!array_key_exists('transactions', $transactionResponse) || !array_key_exists('booked', $transactionResponse["transactions"])) {
throw new \Exception('invalid dataset'); throw new \Exception('invalid dataset');
}
foreach ($transactionResponse["transactions"]["booked"] as $transaction) { foreach ($transactionResponse["transactions"]["booked"] as $transaction) {
$data[] = $this->transformTransaction($transaction); $data[] = $this->transformTransaction($transaction);
@ -83,11 +94,11 @@ class TransactionTransformer implements BankRevenueInterface
{ {
// depending on institution, the result can be different, so we load the first available unique id // depending on institution, the result can be different, so we load the first available unique id
$transactionId = ''; $transactionId = '';
if (array_key_exists('transactionId', $transaction)) if (array_key_exists('transactionId', $transaction)) {
$transactionId = $transaction["transactionId"]; $transactionId = $transaction["transactionId"];
else if (array_key_exists('internalTransactionId', $transaction)) } elseif (array_key_exists('internalTransactionId', $transaction)) {
$transactionId = $transaction["internalTransactionId"]; $transactionId = $transaction["internalTransactionId"];
else { } else {
nlog(`Invalid Input for nordigen transaction transformer: ` . $transaction); nlog(`Invalid Input for nordigen transaction transformer: ` . $transaction);
throw new \Exception('invalid dataset: missing transactionId - Please report this error to the developer'); throw new \Exception('invalid dataset: missing transactionId - Please report this error to the developer');
} }
@ -96,23 +107,25 @@ class TransactionTransformer implements BankRevenueInterface
// description could be in varios places // description could be in varios places
$description = ''; $description = '';
if (array_key_exists('remittanceInformationStructured', $transaction)) if (array_key_exists('remittanceInformationStructured', $transaction)) {
$description = $transaction["remittanceInformationStructured"]; $description = $transaction["remittanceInformationStructured"];
else if (array_key_exists('remittanceInformationStructuredArray', $transaction)) } elseif (array_key_exists('remittanceInformationStructuredArray', $transaction)) {
$description = implode('\n', $transaction["remittanceInformationStructuredArray"]); $description = implode('\n', $transaction["remittanceInformationStructuredArray"]);
else if (array_key_exists('remittanceInformationUnstructured', $transaction)) } elseif (array_key_exists('remittanceInformationUnstructured', $transaction)) {
$description = $transaction["remittanceInformationUnstructured"]; $description = $transaction["remittanceInformationUnstructured"];
else if (array_key_exists('remittanceInformationUnstructuredArray', $transaction)) } elseif (array_key_exists('remittanceInformationUnstructuredArray', $transaction)) {
$description = implode('\n', $transaction["remittanceInformationUnstructuredArray"]); $description = implode('\n', $transaction["remittanceInformationUnstructuredArray"]);
else } else {
Log::warning("Missing description for the following transaction: " . json_encode($transaction)); Log::warning("Missing description for the following transaction: " . json_encode($transaction));
}
// enrich description with currencyExchange informations // enrich description with currencyExchange informations
if (array_key_exists('currencyExchange', $transaction)) if (isset($transaction['currencyExchange'])) {
foreach ($transaction["currencyExchange"] as $exchangeRate) { foreach ($transaction["currencyExchange"] as $exchangeRate) {
$targetAmount = round($amount * (float) $exchangeRate["exchangeRate"], 2); $targetAmount = round($amount * (float) ($exchangeRate["exchangeRate"] ?? 1), 2);
$description .= '\nexchangeRate: ' . $amount . " " . $exchangeRate["sourceCurrency"] . " = " . $targetAmount . " " . $exchangeRate["targetCurrency"] . " (" . $exchangeRate["quotationDate"] . ")"; $description .= '\n' . ctrans('texts.exchange_rate') . ' : ' . $amount . " " . ($exchangeRate["sourceCurrency"] ?? '?') . " = " . $targetAmount . " " . ($exchangeRate["targetCurrency"] ?? '?') . " (" . (isset($exchangeRate["quotationDate"]) ? $this->formatDate($exchangeRate["quotationDate"]) : '?') . ")";
} }
}
// participant data // participant data
$participant = array_key_exists('debtorAccount', $transaction) && array_key_exists('iban', $transaction["debtorAccount"]) ? $participant = array_key_exists('debtorAccount', $transaction) && array_key_exists('iban', $transaction["debtorAccount"]) ?
@ -153,11 +166,32 @@ class TransactionTransformer implements BankRevenueInterface
return $item->code == $code; return $item->code == $code;
})->first(); })->first();
if ($currency) if ($currency) {
return $currency->id; return $currency->id;
}
return 1; return 1;
} }
private function formatDate(string $input)
{
$timezone = Timezone::find($this->company->settings->timezone_id);
$timezone_name = 'US/Eastern';
if ($timezone) {
$timezone_name = $timezone->name;
}
$date_format_default = 'Y-m-d';
$date_format = DateFormat::find($this->company->settings->date_format_id);
if ($date_format) {
$date_format_default = $date_format->format;
}
return Carbon::createFromFormat("d-m-Y", $input)->setTimezone($timezone_name)->format($date_format_default) ?? $input;
}
} }

View File

@ -242,9 +242,9 @@ class InvoiceSum
if ($this->invoice->status_id != Invoice::STATUS_DRAFT) { if ($this->invoice->status_id != Invoice::STATUS_DRAFT) {
if ($this->invoice->amount != $this->invoice->balance) { if ($this->invoice->amount != $this->invoice->balance) {
$paid_to_date = $this->invoice->amount - $this->invoice->balance; // $paid_to_date = $this->invoice->amount - $this->invoice->balance;
$this->invoice->balance = Number::roundValue($this->getTotal(), $this->precision) - $paid_to_date; $this->invoice->balance = Number::roundValue($this->getTotal(), $this->precision) - $this->invoice->paid_to_date; //21-02-2024 cannot use the calculated $paid_to_date here as it could send the balance backward.
} else { } else {
$this->invoice->balance = Number::roundValue($this->getTotal(), $this->precision); $this->invoice->balance = Number::roundValue($this->getTotal(), $this->precision);
} }

View File

@ -259,9 +259,9 @@ class InvoiceSumInclusive
/* If amount != balance then some money has been paid on the invoice, need to subtract this difference from the total to set the new balance */ /* If amount != balance then some money has been paid on the invoice, need to subtract this difference from the total to set the new balance */
if ($this->invoice->status_id != Invoice::STATUS_DRAFT) { if ($this->invoice->status_id != Invoice::STATUS_DRAFT) {
if ($this->invoice->amount != $this->invoice->balance) { if ($this->invoice->amount != $this->invoice->balance) {
$paid_to_date = $this->invoice->amount - $this->invoice->balance; // $paid_to_date = $this->invoice->amount - $this->invoice->balance;
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision) - $paid_to_date; $this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision) - $this->invoice->paid_to_date;
} else { } else {
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision); $this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision);
} }

View File

@ -62,7 +62,7 @@ class ActivityController extends BaseController
$system = ctrans('texts.system'); $system = ctrans('texts.system');
$data = $activities->cursor()->map(function ($activity) { $data = $activities->cursor()->map(function ($activity) {
/** @var \App\Models\Activity $activity */ /** @var \App\Models\Activity $activity */
return $activity->activity_string(); return $activity->activity_string();

View File

@ -112,8 +112,9 @@ class BankTransactionController extends BaseController
$this->bank_transaction_repo->convert_matched($bank_transactions); $this->bank_transaction_repo->convert_matched($bank_transactions);
} else { } else {
$bank_transactions->each(function ($bank_transaction, $key) use ($action, $user) { $bank_transactions->each(function ($bank_transaction, $key) use ($action, $user) {
if($user->can('edit', $bank_transaction)) if($user->can('edit', $bank_transaction)) {
$this->bank_transaction_repo->{$action}($bank_transaction); $this->bank_transaction_repo->{$action}($bank_transaction);
}
}); });
} }

View File

@ -140,6 +140,7 @@ class BaseController extends Controller
'company.quotes.invitations.company', 'company.quotes.invitations.company',
'company.quotes.documents', 'company.quotes.documents',
'company.tasks.documents', 'company.tasks.documents',
// 'company.tasks.project',
'company.subscriptions', 'company.subscriptions',
'company.tax_rates', 'company.tax_rates',
'company.tokens_hashed', 'company.tokens_hashed',
@ -458,7 +459,7 @@ class BaseController extends Controller
} }
}, },
'company.tasks' => function ($query) use ($updated_at, $user) { 'company.tasks' => function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('documents'); $query->where('updated_at', '>=', $updated_at)->with('project','documents');
if (! $user->hasPermission('view_task')) { if (! $user->hasPermission('view_task')) {
$query->whereNested(function ($query) use ($user) { $query->whereNested(function ($query) use ($user) {
@ -796,7 +797,7 @@ class BaseController extends Controller
} }
}, },
'company.tasks' => function ($query) use ($created_at, $user) { 'company.tasks' => function ($query) use ($created_at, $user) {
$query->where('created_at', '>=', $created_at)->with('documents'); $query->where('created_at', '>=', $created_at)->with('project.documents','documents');
if (! $user->hasPermission('view_task')) { if (! $user->hasPermission('view_task')) {
$query->whereNested(function ($query) use ($user) { $query->whereNested(function ($query) use ($user) {

View File

@ -11,37 +11,46 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Events\Client\ClientWasCreated; use App\Utils\Ninja;
use App\Events\Client\ClientWasUpdated; use App\Models\Quote;
use App\Models\Client;
use App\Models\Credit;
use App\Models\Account;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\Document;
use App\Models\SystemLog;
use Postmark\PostmarkClient;
use Illuminate\Http\Response;
use App\Factory\ClientFactory; use App\Factory\ClientFactory;
use App\Filters\ClientFilters; use App\Filters\ClientFilters;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\Uploadable;
use App\Utils\Traits\BulkOptions;
use App\Jobs\Client\UpdateTaxData;
use App\Utils\Traits\SavesDocuments;
use App\Repositories\ClientRepository;
use App\Events\Client\ClientWasCreated;
use App\Events\Client\ClientWasUpdated;
use App\Transformers\ClientTransformer;
use Illuminate\Support\Facades\Storage;
use App\Services\Template\TemplateAction;
use App\Jobs\PostMark\ProcessPostmarkWebhook;
use App\Http\Requests\Client\BulkClientRequest; use App\Http\Requests\Client\BulkClientRequest;
use App\Http\Requests\Client\CreateClientRequest;
use App\Http\Requests\Client\DestroyClientRequest;
use App\Http\Requests\Client\EditClientRequest; use App\Http\Requests\Client\EditClientRequest;
use App\Http\Requests\Client\PurgeClientRequest;
use App\Http\Requests\Client\ReactivateClientEmailRequest;
use App\Http\Requests\Client\ShowClientRequest; use App\Http\Requests\Client\ShowClientRequest;
use App\Http\Requests\Client\PurgeClientRequest;
use App\Http\Requests\Client\StoreClientRequest; use App\Http\Requests\Client\StoreClientRequest;
use App\Http\Requests\Client\CreateClientRequest;
use App\Http\Requests\Client\UpdateClientRequest; use App\Http\Requests\Client\UpdateClientRequest;
use App\Http\Requests\Client\UploadClientRequest; use App\Http\Requests\Client\UploadClientRequest;
use App\Jobs\Client\UpdateTaxData; use App\Http\Requests\Client\DestroyClientRequest;
use App\Jobs\PostMark\ProcessPostmarkWebhook; use App\Http\Requests\Client\ClientDocumentsRequest;
use App\Models\Account; use App\Http\Requests\Client\ReactivateClientEmailRequest;
use App\Models\Client; use App\Models\Expense;
use App\Models\Company; use App\Models\Payment;
use App\Models\SystemLog; use App\Models\Task;
use App\Repositories\ClientRepository; use App\Transformers\DocumentTransformer;
use App\Services\Template\TemplateAction;
use App\Transformers\ClientTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\BulkOptions;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use App\Utils\Traits\Uploadable;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use Postmark\PostmarkClient;
/** /**
* Class ClientController. * Class ClientController.
@ -402,4 +411,24 @@ class ClientController extends BaseController
} }
} }
public function documents(ClientDocumentsRequest $request, Client $client)
{
$this->entity_type = Document::class;
$this->entity_transformer = DocumentTransformer::class;
$documents = Document::query()
->company()
->whereHasMorph('documentable', [Invoice::class, Quote::class, Credit::class, Expense::class, Payment::class, Task::class], function ($query) use ($client) {
$query->where('client_id', $client->id);
})
->orWhereHasMorph('documentable', [Client::class], function ($query) use ($client) {
$query->where('id', $client->id);
});
return $this->listResponse($documents);
}
} }

View File

@ -353,7 +353,7 @@ class ClientGatewayTokenController extends BaseController
*/ */
public function store(StoreClientGatewayTokenRequest $request) public function store(StoreClientGatewayTokenRequest $request)
{ {
/** @var \App\Models\User $user */ /** @var \App\Models\User $user */
$user = auth()->user(); $user = auth()->user();

View File

@ -73,12 +73,13 @@ class DocumentController extends Controller
{ {
$hash = Cache::pull($hash); $hash = Cache::pull($hash);
if(!$hash) if(!$hash) {
abort(404); abort(404);
}
MultiDB::setDb($hash['db']); MultiDB::setDb($hash['db']);
/** @var \App\Models\Document $document **/ /** @var \App\Models\Document $document **/
$document = Document::where('hash', $hash['doc_hash'])->firstOrFail(); $document = Document::where('hash', $hash['doc_hash'])->firstOrFail();

View File

@ -58,4 +58,3 @@ class EmailPreferencesController extends Controller
return back()->with('message', ctrans('texts.updated_settings')); return back()->with('message', ctrans('texts.updated_settings'));
} }
} }

View File

@ -70,7 +70,7 @@ class InvoiceController extends Controller
} }
$variables = ($invitation && auth()->guard('contact')->user()->client->getSetting('show_accept_invoice_terms')) ? (new HtmlEngine($invitation))->generateLabelsAndValues() : false; $variables = ($invitation && auth()->guard('contact')->user()->client->getSetting('show_accept_invoice_terms')) ? (new HtmlEngine($invitation))->generateLabelsAndValues() : false;
$data = [ $data = [
'invoice' => $invoice, 'invoice' => $invoice,
'invitation' => $invitation ?: $invoice->invitations->first(), 'invitation' => $invitation ?: $invoice->invitations->first(),
@ -224,8 +224,9 @@ class InvoiceController extends Controller
$settings = auth()->guard('contact')->user()->client->getMergedSettings(); $settings = auth()->guard('contact')->user()->client->getMergedSettings();
$variables = false; $variables = false;
if(($invitation = $invoices->first()->invitations()->first() ?? false) && $settings->show_accept_invoice_terms) if(($invitation = $invoices->first()->invitations()->first() ?? false) && $settings->show_accept_invoice_terms) {
$variables = (new HtmlEngine($invitation))->generateLabelsAndValues(); $variables = (new HtmlEngine($invitation))->generateLabelsAndValues();
}
$data = [ $data = [
'settings' => $settings, 'settings' => $settings,
@ -235,7 +236,6 @@ class InvoiceController extends Controller
'hashed_ids' => $invoices->pluck('hashed_id'), 'hashed_ids' => $invoices->pluck('hashed_id'),
'total' => $total, 'total' => $total,
'variables' => $variables, 'variables' => $variables,
]; ];
return $this->render('invoices.payment', $data); return $this->render('invoices.payment', $data);

View File

@ -12,24 +12,25 @@
namespace App\Http\Controllers\ClientPortal; namespace App\Http\Controllers\ClientPortal;
use App\Factory\PaymentFactory;
use App\Http\Controllers\Controller;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Models\CompanyGateway;
use App\Models\GatewayType;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Payment; use App\Models\Payment;
use App\Utils\HtmlEngine;
use Illuminate\View\View;
use App\Models\GatewayType;
use App\Models\PaymentHash; use App\Models\PaymentHash;
use App\Models\PaymentType; use App\Models\PaymentType;
use Illuminate\Http\Request;
use App\Models\CompanyGateway;
use App\Factory\PaymentFactory;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesDates;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Contracts\View\Factory;
use App\PaymentDrivers\Stripe\BankTransfer; use App\PaymentDrivers\Stripe\BankTransfer;
use App\Services\ClientPortal\InstantPayment; use App\Services\ClientPortal\InstantPayment;
use App\Services\Subscription\SubscriptionService; use App\Services\Subscription\SubscriptionService;
use App\Utils\Traits\MakesDates; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
/** /**
* Class PaymentController. * Class PaymentController.
@ -106,6 +107,12 @@ class PaymentController extends Controller
*/ */
public function process(Request $request) public function process(Request $request)
{ {
$request->validate([
'contact_first_name' => ['required'],
'contact_last_name' => ['required'],
'contact_email' => ['required', 'email'],
]);
return (new InstantPayment($request))->run(); return (new InstantPayment($request))->run();
} }
@ -119,10 +126,16 @@ class PaymentController extends Controller
// 09-07-2022 catch duplicate responses for invoices that already paid here. // 09-07-2022 catch duplicate responses for invoices that already paid here.
if ($invoice && $invoice->status_id == Invoice::STATUS_PAID) { if ($invoice && $invoice->status_id == Invoice::STATUS_PAID) {
$invitation = $invoice->invitations->first();
$variables = ($invitation && auth()->guard('contact')->user()->client->getSetting('show_accept_invoice_terms')) ? (new HtmlEngine($invitation))->generateLabelsAndValues() : false;
$data = [ $data = [
'invoice' => $invoice, 'invoice' => $invoice,
'key' => false, 'key' => false,
'invitation' => $invoice->invitations->first() 'invitation' => $invitation,
'variables' => $variables,
]; ];
if ($request->query('mode') === 'fullscreen') { if ($request->query('mode') === 'fullscreen') {

View File

@ -144,7 +144,10 @@ class PaymentMethodController extends Controller
try { try {
event(new MethodDeleted($payment_method, auth()->guard('contact')->user()->company, Ninja::eventVars(auth()->guard('contact')->user()->id))); event(new MethodDeleted($payment_method, auth()->guard('contact')->user()->company, Ninja::eventVars(auth()->guard('contact')->user()->id)));
$payment_method->is_deleted = true;
$payment_method->delete(); $payment_method->delete();
$payment_method->save();
} catch (Exception $e) { } catch (Exception $e) {
nlog($e->getMessage()); nlog($e->getMessage());

View File

@ -12,16 +12,17 @@
namespace App\Http\Controllers\ClientPortal; namespace App\Http\Controllers\ClientPortal;
use App\Utils\Number;
use App\Utils\HtmlEngine;
use Illuminate\View\View;
use App\DataMapper\InvoiceItem; use App\DataMapper\InvoiceItem;
use App\Factory\InvoiceFactory; use App\Factory\InvoiceFactory;
use App\Http\Controllers\Controller;
use App\Http\Requests\ClientPortal\PrePayments\StorePrePaymentRequest;
use App\Repositories\InvoiceRepository;
use App\Utils\Number;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesDates;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
use Illuminate\View\View; use App\Repositories\InvoiceRepository;
use App\Http\Requests\ClientPortal\PrePayments\StorePrePaymentRequest;
/** /**
* Class PrePaymentController. * Class PrePaymentController.
@ -88,6 +89,8 @@ class PrePaymentController extends Controller
$total = $invoice->balance; $total = $invoice->balance;
$invitation = $invoice->invitations->first();
//format totals //format totals
$formatted_total = Number::formatMoney($invoice->amount, auth()->guard('contact')->user()->client); $formatted_total = Number::formatMoney($invoice->amount, auth()->guard('contact')->user()->client);
@ -113,6 +116,8 @@ class PrePaymentController extends Controller
'frequency_id' => $request->frequency_id, 'frequency_id' => $request->frequency_id,
'remaining_cycles' => $request->remaining_cycles, 'remaining_cycles' => $request->remaining_cycles,
'is_recurring' => $request->is_recurring == 'on' ? true : false, 'is_recurring' => $request->is_recurring == 'on' ? true : false,
'variables' => $variables = ($invitation && auth()->guard('contact')->user()->client->getSetting('show_accept_invoice_terms')) ? (new HtmlEngine($invitation))->generateLabelsAndValues() : false,
]; ];
return $this->render('invoices.payment', $data); return $this->render('invoices.payment', $data);

View File

@ -215,7 +215,7 @@ class QuoteController extends Controller
->withSuccess('Quote(s) approved successfully.'); ->withSuccess('Quote(s) approved successfully.');
} }
$variables = false; $variables = false;
if($invitation = $quotes->first()->invitations()->first() ?? false) { if($invitation = $quotes->first()->invitations()->first() ?? false) {

View File

@ -149,7 +149,7 @@ class CompanyGatewayController extends BaseController
*/ */
public function create(CreateCompanyGatewayRequest $request) public function create(CreateCompanyGatewayRequest $request)
{ {
/** @var \App\Models\User $user */ /** @var \App\Models\User $user */
$user = auth()->user(); $user = auth()->user();

View File

@ -64,7 +64,7 @@ class CompanyLedgerController extends BaseController
*/ */
public function index(ShowCompanyLedgerRequest $request) public function index(ShowCompanyLedgerRequest $request)
{ {
/** @var \App\Models\User $user */ /** @var \App\Models\User $user */
$user = auth()->user(); $user = auth()->user();

View File

@ -121,7 +121,7 @@ class ConnectedAccountController extends BaseController
'email_verified_at' => now() 'email_verified_at' => now()
]; ];
/** @var \App\Models\User $user */ /** @var \App\Models\User $user */
$user = auth()->user(); $user = auth()->user();
@ -169,13 +169,16 @@ class ConnectedAccountController extends BaseController
'email_verified_at' => now(), 'email_verified_at' => now(),
]; ];
auth()->user()->update($connected_account); /** @var \App\Models\User $logged_in_user */
auth()->user()->email_verified_at = now(); $logged_in_user = auth()->user();
auth()->user()->save();
$this->setLoginCache(auth()->user()); $logged_in_user->update($connected_account);
$logged_in_user->email_verified_at = now();
$logged_in_user->save();
return $this->itemResponse(auth()->user()); $this->setLoginCache($logged_in_user);
return $this->itemResponse($logged_in_user);
} }
return response() return response()
@ -214,20 +217,22 @@ class ConnectedAccountController extends BaseController
// 'email_verified_at' =>now(), // 'email_verified_at' =>now(),
]; ];
if (auth()->user()->email != $google->harvestEmail($user)) { /** @var \App\Models\User $logged_in_user */
$logged_in_user = auth()->user();
if ($logged_in_user->email != $google->harvestEmail($user)) {
return response()->json(['message' => 'Primary Email differs to OAuth email. Emails must match.'], 400); return response()->json(['message' => 'Primary Email differs to OAuth email. Emails must match.'], 400);
} }
auth()->user()->update($connected_account); $logged_in_user->update($connected_account);
auth()->user()->email_verified_at = now(); $logged_in_user->email_verified_at = now();
auth()->user()->oauth_user_token = $token; $logged_in_user->oauth_user_token = $token;
auth()->user()->oauth_user_refresh_token = $refresh_token; $logged_in_user->oauth_user_refresh_token = $refresh_token;
$logged_in_user->save();
auth()->user()->save(); $this->activateGmail($logged_in_user);
$this->activateGmail(auth()->user()); return $this->itemResponse($logged_in_user);
return $this->itemResponse(auth()->user());
} }
return response() return response()

View File

@ -128,7 +128,7 @@ class ExpenseCategoryController extends BaseController
*/ */
public function create(CreateExpenseCategoryRequest $request) public function create(CreateExpenseCategoryRequest $request)
{ {
/** @var \App\Models\User $user */ /** @var \App\Models\User $user */
$user = auth()->user(); $user = auth()->user();

View File

@ -21,7 +21,7 @@ class HostedMigrationController extends Controller
{ {
public function checkStatus(Request $request) public function checkStatus(Request $request)
{ {
if ($request->header('X-API-HOSTED-SECRET') != config('ninja.ninja_hosted_secret')) { if ($request->header('X-API-HOSTED-SECRET') != config('ninja.ninja_hosted_secret')) {
return; return;
} }
@ -29,19 +29,20 @@ class HostedMigrationController extends Controller
MultiDB::findAndSetDbByCompanyKey($request->company_key); MultiDB::findAndSetDbByCompanyKey($request->company_key);
$c = Company::where('company_key', $request->company_key)->first(); $c = Company::where('company_key', $request->company_key)->first();
if(!$c || $c->is_disabled) if(!$c || $c->is_disabled) {
return response()->json(['message' => 'ok'], 200); return response()->json(['message' => 'ok'], 200);
}
// if(\App\Models\Invoice::query()->where('company_id', $c->id)->where('created_at', '>', now()->subMonths(2))->first()) // if(\App\Models\Invoice::query()->where('company_id', $c->id)->where('created_at', '>', now()->subMonths(2))->first())
// return response()->json(['message' => 'New data exists, are you sure? Please log in here https://app.invoicing.co and delete the company if you really need to migrate again.'], 400); // return response()->json(['message' => 'New data exists, are you sure? Please log in here https://app.invoicing.co and delete the company if you really need to migrate again.'], 400);
// if(\App\Models\Client::query()->where('company_id', $c->id)->where('created_at', '>', now()->subMonths(2))->first()) // if(\App\Models\Client::query()->where('company_id', $c->id)->where('created_at', '>', now()->subMonths(2))->first())
// return response()->json(['message' => 'New data exists, are you sure? Please log in here https://app.invoicing.co and delete the company if you really need to migrate again.'], 400);
// if(\App\Models\Quote::query()->where('company_id', $c->id)->where('created_at', '>', now()->subMonths(2)))
// return response()->json(['message' => 'New data exists, are you sure? Please log in here https://app.invoicing.co and delete the company if you really need to migrate again.'], 400); // return response()->json(['message' => 'New data exists, are you sure? Please log in here https://app.invoicing.co and delete the company if you really need to migrate again.'], 400);
// if(\App\Models\RecurringInvoice::query()->where('company_id', $c->id)->where('created_at', '>', now()->subMonths(2))) // if(\App\Models\Quote::query()->where('company_id', $c->id)->where('created_at', '>', now()->subMonths(2)))
// return response()->json(['message' => 'New data exists, are you sure? Please log in here https://app.invoicing.co and delete the company if you really need to migrate again.'], 400);
// if(\App\Models\RecurringInvoice::query()->where('company_id', $c->id)->where('created_at', '>', now()->subMonths(2)))
// return response()->json(['message' => 'New data exists, are you sure? Please log in here https://app.invoicing.co and delete the company if you really need to migrate again.'], 400); // return response()->json(['message' => 'New data exists, are you sure? Please log in here https://app.invoicing.co and delete the company if you really need to migrate again.'], 400);
return response()->json(['message' => 'You have already activated this company on v5!!!!!! This migration may be a BAD idea. Contact us contact@invoiceninja.com to confirm this action.'], 400); return response()->json(['message' => 'You have already activated this company on v5!!!!!! This migration may be a BAD idea. Contact us contact@invoiceninja.com to confirm this action.'], 400);

View File

@ -244,19 +244,23 @@ class ImportController extends Controller
*/ */
public function detectDelimiter($csvfile): string public function detectDelimiter($csvfile): string
{ {
$delimiters = [',', '.', ';'];
$bestDelimiter = ' '; $delimiters = [',', '.', ';', '|'];
$bestDelimiter = ',';
$count = 0; $count = 0;
// 10-01-2024 - A better way to resolve the csv file delimiter.
$csvfile = substr($csvfile, 0, strpos($csvfile, "\n"));
foreach ($delimiters as $delimiter) { foreach ($delimiters as $delimiter) {
if (substr_count(strstr($csvfile, "\n", true), $delimiter) >= $count) { if (substr_count(strstr($csvfile, "\n", true), $delimiter) >= $count) {
$count = substr_count(strstr($csvfile, "\n", true), $delimiter); $count = substr_count($csvfile, $delimiter);
$bestDelimiter = $delimiter; $bestDelimiter = $delimiter;
} }
} }
return $bestDelimiter ?? ',';
return $bestDelimiter;
} }
} }

View File

@ -22,18 +22,22 @@ class MailgunWebhookController extends BaseController
{ {
private $invitation; private $invitation;
public function __construct() {} public function __construct()
{
}
public function webhook(Request $request) public function webhook(Request $request)
{ {
$input = $request->all(); $input = $request->all();
if (\abs(\time() - $request['signature']['timestamp']) > 15) if (\abs(\time() - $request['signature']['timestamp']) > 15) {
return response()->json(['message' => 'Success'], 200); return response()->json(['message' => 'Success'], 200);
}
if(\hash_equals(\hash_hmac('sha256', $input['signature']['timestamp'] . $input['signature']['token'], config('services.mailgun.webhook_signing_key')), $input['signature']['signature'])) if(\hash_equals(\hash_hmac('sha256', $input['signature']['timestamp'] . $input['signature']['token'], config('services.mailgun.webhook_signing_key')), $input['signature']['signature'])) {
ProcessMailgunWebhook::dispatch($request->all())->delay(10); ProcessMailgunWebhook::dispatch($request->all())->delay(10);
}
return response()->json(['message' => 'Success.'], 200); return response()->json(['message' => 'Success.'], 200);
} }

View File

@ -520,7 +520,7 @@ class PaymentController extends BaseController
if($action == 'template' && $user->can('view', $payments->first())) { if($action == 'template' && $user->can('view', $payments->first())) {
$hash_or_response = request()->boolean('send_email') ? 'email sent' : \Illuminate\Support\Str::uuid(); $hash_or_response = request()->boolean('send_email') ? 'email sent' : \Illuminate\Support\Str::uuid();
TemplateAction::dispatch( TemplateAction::dispatch(
$payments->pluck('hashed_id')->toArray(), $payments->pluck('hashed_id')->toArray(),
$request->template_id, $request->template_id,

View File

@ -316,8 +316,8 @@ class PreviewPurchaseOrderController extends BaseController
return; return;
} }
/** @var \App\Models\User $user */ /** @var \App\Models\User $user */
$user = auth()->user(); $user = auth()->user();
//if phantom js...... inject here.. //if phantom js...... inject here..
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {

View File

@ -181,7 +181,7 @@ class ProductController extends BaseController
*/ */
public function store(StoreProductRequest $request) public function store(StoreProductRequest $request)
{ {
/** @var \App\Models\User $user */ /** @var \App\Models\User $user */
$user = auth()->user(); $user = auth()->user();

View File

@ -399,7 +399,7 @@ class QuoteController extends BaseController
$quote->service() $quote->service()
->triggeredActions($request); ->triggeredActions($request);
event(new QuoteWasUpdated($quote, $quote->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); event(new QuoteWasUpdated($quote, $quote->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($quote); return $this->itemResponse($quote);

View File

@ -0,0 +1,62 @@
<?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\Controllers;
use App\Http\Requests\Smtp\CheckSmtpRequest;
use App\Mail\TestMailServer;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
class SmtpController extends BaseController
{
public function __construct()
{
parent::__construct();
}
public function check(CheckSmtpRequest $request)
{
/** @var \App\Models\User $user */
$user = auth()->user();
$company = $user->company();
config([
'mail.mailers.smtp' => [
'transport' => 'smtp',
'host' => $request->input('smtp_host', $company->smtp_host),
'port' => $request->input('smtp_port', $company->smtp_port),
'username' => $request->input('smtp_username', $company->smtp_username),
'password' => $request->input('smtp_password', $company->smtp_password),
'encryption' => $request->input('smtp_encryption', $company->smtp_encryption ?? 'tls'),
'local_domain' => $request->input('smtp_local_domain', strlen($company->smtp_local_domain) > 2 ? $company->smtp_local_domain : null),
'verify_peer' => $request->input('verify_peer', $company->smtp_verify_peer ?? true),
'timeout' => 5,
],
]);
(new \Illuminate\Mail\MailServiceProvider(app()))->register();
try {
Mail::to($user->email, $user->present()->name())->send(new TestMailServer('Email Server Works!', strlen($company->settings->custom_sending_email) > 1 ? $company->settings->custom_sending_email : $user->email));
} catch (\Exception $e) {
app('mail.manager')->forgetMailers();
return response()->json(['message' => $e->getMessage()], 400);
}
app('mail.manager')->forgetMailers();
return response()->json(['message' => 'Ok'], 200);
}
}

View File

@ -86,7 +86,7 @@ class StripeConnectController extends BaseController
]); ]);
nlog($response); nlog($response);
} catch (\Exception $e) { } catch (\Exception $e) {
return view('auth.connect.access_denied'); return view('auth.connect.access_denied');
} }

View File

@ -72,6 +72,10 @@ class TwoFactorController extends BaseController
return response()->json(['message' => ctrans('texts.enabled_two_factor')], 200); return response()->json(['message' => ctrans('texts.enabled_two_factor')], 200);
} elseif (! $secret || ! $google2fa->verifyKey($secret, $oneTimePassword)) { } elseif (! $secret || ! $google2fa->verifyKey($secret, $oneTimePassword)) {
return response()->json(['message' => ctrans('texts.invalid_one_time_password')], 400); return response()->json(['message' => ctrans('texts.invalid_one_time_password')], 400);
}elseif (! $user->phone) {
return response()->json(['message' => ctrans('texts.set_phone_for_two_factor')], 400);
} elseif (! $user->isVerified()) {
return response()->json(['message' => 'Please confirm your account first'], 400);
} }
return response()->json(['message' => 'No phone record or user is not confirmed'], 400); return response()->json(['message' => 'No phone record or user is not confirmed'], 400);
@ -84,7 +88,7 @@ class TwoFactorController extends BaseController
public function disableTwoFactor() public function disableTwoFactor()
{ {
/** @var \App\Models\User $user */ /** @var \App\Models\User $user */
$user = auth()->user(); $user = auth()->user();

View File

@ -499,7 +499,7 @@ class VendorController extends BaseController
$ids = request()->input('ids'); $ids = request()->input('ids');
$vendors = Vendor::withTrashed()->find($this->transformKeys($ids)); $vendors = Vendor::withTrashed()->find($this->transformKeys($ids));
/** @var \App\Models\User $user */ /** @var \App\Models\User $user */
$user = auth()->user(); $user = auth()->user();

View File

@ -67,13 +67,14 @@ class TokenAuth
$truth->setUser($company_token->user); $truth->setUser($company_token->user);
$truth->setCompany($company_token->company); $truth->setCompany($company_token->company);
$truth->setCompanyToken($company_token); $truth->setCompanyToken($company_token);
$truth->setPremiumHosted($company_token->account->isPremium());
/* /*
| This method binds the db to the jobs created using this | This method binds the db to the jobs created using this
| session | session
*/ */
app('queue')->createPayloadUsing(function () use ($company_token) { app('queue')->createPayloadUsing(function () use ($company_token) {
return ['db' => $company_token->company->db]; return ['db' => $company_token->company->db];
// return ['db' => $company_token->company->db, 'is_premium' => $company_token->account->isPremium()];
}); });
//user who once existed, but has been soft deleted //user who once existed, but has been soft deleted

View File

@ -0,0 +1,30 @@
<?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\Client;
use App\Http\Requests\Request;
class ClientDocumentsRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->can('view', $this->client);
}
}

View File

@ -22,6 +22,9 @@ class PurgeClientRequest extends Request
*/ */
public function authorize(): bool public function authorize(): bool
{ {
return auth()->user()->isAdmin(); /** @var \App\Models\User $user */
$user = auth()->user();
return $user->isAdmin();
} }
} }

View File

@ -49,6 +49,9 @@ class StoreClientRequest extends Request
} elseif ($this->file('documents')) { } elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation; $rules['documents'] = $this->file_validation;
} }
else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) { if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation; $rules['file.*'] = $this->file_validation;
@ -93,7 +96,7 @@ class StoreClientRequest extends Request
$rules['number'] = ['bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)]; $rules['number'] = ['bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)];
$rules['id_number'] = ['bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)]; $rules['id_number'] = ['bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)];
$rules['classification'] = 'bail|sometimes|nullable|in:individual,business,partnership,trust,charity,government,other'; $rules['classification'] = 'bail|sometimes|nullable|in:individual,business,company,partnership,trust,charity,government,other';
return $rules; return $rules;
} }

View File

@ -53,6 +53,8 @@ class UpdateClientRequest extends Request
$rules['file.*'] = $this->file_validation; $rules['file.*'] = $this->file_validation;
} elseif ($this->file('file')) { } elseif ($this->file('file')) {
$rules['file'] = $this->file_validation; $rules['file'] = $this->file_validation;
} else {
$rules['documents'] = 'bail|sometimes|array';
} }
$rules['company_logo'] = 'mimes:jpeg,jpg,png,gif|max:10000'; $rules['company_logo'] = 'mimes:jpeg,jpg,png,gif|max:10000';
@ -60,7 +62,7 @@ class UpdateClientRequest extends Request
$rules['size_id'] = 'integer|nullable'; $rules['size_id'] = 'integer|nullable';
$rules['country_id'] = 'integer|nullable'; $rules['country_id'] = 'integer|nullable';
$rules['shipping_country_id'] = 'integer|nullable'; $rules['shipping_country_id'] = 'integer|nullable';
$rules['classification'] = 'bail|sometimes|nullable|in:individual,business,partnership,trust,charity,government,other'; $rules['classification'] = 'bail|sometimes|nullable|in:individual,business,company,partnership,trust,charity,government,other';
if ($this->id_number) { if ($this->id_number) {
$rules['id_number'] = Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id); $rules['id_number'] = Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id);

View File

@ -27,7 +27,7 @@ class CreatePaymentMethodRequest extends FormRequest
->filter(function ($method) use (&$available_methods) { ->filter(function ($method) use (&$available_methods) {
$available_methods[] = $method['gateway_type_id']; $available_methods[] = $method['gateway_type_id'];
}); });
if (in_array($this->query('method'), $available_methods)) { if (in_array($this->query('method'), $available_methods)) {
return true; return true;
} }

View File

@ -56,6 +56,15 @@ class StoreCompanyRequest extends Request
} }
} }
$rules['smtp_host'] = 'sometimes|string|nullable';
$rules['smtp_port'] = 'sometimes|integer|nullable';
$rules['smtp_encryption'] = 'sometimes|string';
$rules['smtp_local_domain'] = 'sometimes|string|nullable';
$rules['smtp_encryption'] = 'sometimes|string|nullable';
$rules['smtp_local_domain'] = 'sometimes|string|nullable';
// $rules['smtp_verify_peer'] = 'sometimes|in:true,false';
return $rules; return $rules;
} }
@ -67,11 +76,11 @@ class StoreCompanyRequest extends Request
$input['name'] = 'Untitled Company'; $input['name'] = 'Untitled Company';
} }
if (array_key_exists('google_analytics_url', $input)) { if (isset($input['google_analytics_url'])) {
$input['google_analytics_key'] = $input['google_analytics_url']; $input['google_analytics_key'] = $input['google_analytics_url'];
} }
if (array_key_exists('portal_domain', $input)) { if (isset($input['portal_domain'])) {
$input['portal_domain'] = rtrim(strtolower($input['portal_domain']), "/"); $input['portal_domain'] = rtrim(strtolower($input['portal_domain']), "/");
} }
@ -79,6 +88,21 @@ class StoreCompanyRequest extends Request
$input['subdomain'] = MultiDB::randomSubdomainGenerator(); $input['subdomain'] = MultiDB::randomSubdomainGenerator();
} }
if(isset($input['smtp_username']) && strlen(str_replace("*", "", $input['smtp_username'])) < 2) {
unset($input['smtp_username']);
}
if(isset($input['smtp_password']) && strlen(str_replace("*", "", $input['smtp_password'])) < 2) {
unset($input['smtp_password']);
}
if(isset($input['smtp_port'])) {
$input['smtp_port'] = (int) $input['smtp_port'];
}
if(isset($input['smtp_verify_peer']) && is_string($input['smtp_verify_peer']))
$input['smtp_verify_peer'] == 'true' ? true : false;
$this->replace($input); $this->replace($input);
} }
} }

View File

@ -57,8 +57,14 @@ class UpdateCompanyRequest extends Request
$rules['matomo_id'] = 'nullable|integer'; $rules['matomo_id'] = 'nullable|integer';
$rules['e_invoice_certificate_passphrase'] = 'sometimes|nullable'; $rules['e_invoice_certificate_passphrase'] = 'sometimes|nullable';
$rules['e_invoice_certificate'] = 'sometimes|nullable|file|mimes:p12,pfx,pem,cer,crt,der,txt,p7b,spc,bin'; $rules['e_invoice_certificate'] = 'sometimes|nullable|file|mimes:p12,pfx,pem,cer,crt,der,txt,p7b,spc,bin';
// $rules['client_registration_fields'] = 'array';
$rules['smtp_host'] = 'sometimes|string|nullable';
$rules['smtp_port'] = 'sometimes|integer|nullable';
$rules['smtp_encryption'] = 'sometimes|string|nullable';
$rules['smtp_local_domain'] = 'sometimes|string|nullable';
// $rules['smtp_verify_peer'] = 'sometimes|string';
if (isset($input['portal_mode']) && ($input['portal_mode'] == 'domain' || $input['portal_mode'] == 'iframe')) { if (isset($input['portal_mode']) && ($input['portal_mode'] == 'domain' || $input['portal_mode'] == 'iframe')) {
$rules['portal_domain'] = 'bail|nullable|sometimes|url'; $rules['portal_domain'] = 'bail|nullable|sometimes|url';
} }
@ -74,23 +80,39 @@ class UpdateCompanyRequest extends Request
{ {
$input = $this->all(); $input = $this->all();
if (array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1) { if (isset($input['portal_domain']) && strlen($input['portal_domain']) > 1) {
$input['portal_domain'] = $this->addScheme($input['portal_domain']); $input['portal_domain'] = $this->addScheme($input['portal_domain']);
$input['portal_domain'] = rtrim(strtolower($input['portal_domain']), "/"); $input['portal_domain'] = rtrim(strtolower($input['portal_domain']), "/");
} }
if (array_key_exists('settings', $input)) { if (isset($input['settings'])) {
$input['settings'] = (array)$this->filterSaveableSettings($input['settings']); $input['settings'] = (array)$this->filterSaveableSettings($input['settings']);
} }
if(array_key_exists('subdomain', $input) && $this->company->subdomain == $input['subdomain']) { if(isset($input['subdomain']) && $this->company->subdomain == $input['subdomain']) {
unset($input['subdomain']); unset($input['subdomain']);
} }
if(array_key_exists('e_invoice_certificate_passphrase', $input) && empty($input['e_invoice_certificate_passphrase'])) { if(isset($input['e_invoice_certificate_passphrase']) && empty($input['e_invoice_certificate_passphrase'])) {
unset($input['e_invoice_certificate_passphrase']); unset($input['e_invoice_certificate_passphrase']);
} }
if(isset($input['smtp_username']) && strlen(str_replace("*","", $input['smtp_username'])) < 2) {
unset($input['smtp_username']);
}
if(isset($input['smtp_password']) && strlen(str_replace("*", "", $input['smtp_password'])) < 2) {
unset($input['smtp_password']);
}
if(isset($input['smtp_port'])) {
$input['smtp_port'] = (int)$input['smtp_port'];
}
if(isset($input['smtp_verify_peer']) && is_string($input['smtp_verify_peer'])) {
$input['smtp_verify_peer'] == 'true' ? true : false;
}
$this->replace($input); $this->replace($input);
} }

View File

@ -50,6 +50,8 @@ class StoreCreditRequest extends Request
$rules['documents.*'] = $this->file_validation; $rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) { } elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation; $rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
} }
if ($this->file('file') && is_array($this->file('file'))) { if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -52,6 +52,8 @@ class UpdateCreditRequest extends Request
$rules['documents.*'] = $this->file_validation; $rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) { } elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation; $rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
} }
if ($this->file('file') && is_array($this->file('file'))) { if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -52,6 +52,7 @@ class StoreExpenseRequest extends Request
$rules['category_id'] = 'bail|nullable|sometimes|exists:expense_categories,id,company_id,'.$user->company()->id.',is_deleted,0'; $rules['category_id'] = 'bail|nullable|sometimes|exists:expense_categories,id,company_id,'.$user->company()->id.',is_deleted,0';
$rules['payment_date'] = 'bail|nullable|sometimes|date:Y-m-d'; $rules['payment_date'] = 'bail|nullable|sometimes|date:Y-m-d';
$rules['date'] = 'bail|sometimes|date:Y-m-d'; $rules['date'] = 'bail|sometimes|date:Y-m-d';
$rules['documents'] = 'bail|sometimes|array';
return $this->globalRules($rules); return $this->globalRules($rules);
} }

View File

@ -29,25 +29,32 @@ class UpdateExpenseRequest extends Request
*/ */
public function authorize(): bool public function authorize(): bool
{ {
return auth()->user()->can('edit', $this->expense); /** @var \App\Models\User $user */
$user = auth()->user();
return $user->can('edit', $this->expense);
} }
public function rules() public function rules()
{ {
/** @var \App\Models\User $user */
$user = auth()->user();
/* Ensure we have a client name, and that all emails are unique*/ /* Ensure we have a client name, and that all emails are unique*/
$rules = []; $rules = [];
if (isset($this->number)) { if (isset($this->number)) {
$rules['number'] = Rule::unique('expenses')->where('company_id', auth()->user()->company()->id)->ignore($this->expense->id); $rules['number'] = Rule::unique('expenses')->where('company_id', $user->company()->id)->ignore($this->expense->id);
} }
if ($this->client_id) { if ($this->client_id) {
$rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id; $rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.$user->company()->id;
} }
$rules['category_id'] = 'bail|sometimes|nullable|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; $rules['category_id'] = 'bail|sometimes|nullable|exists:expense_categories,id,company_id,'.$user->company()->id.',is_deleted,0';
$rules['transaction_id'] = 'bail|sometimes|nullable|exists:bank_transactions,id,company_id,'.auth()->user()->company()->id; $rules['transaction_id'] = 'bail|sometimes|nullable|exists:bank_transactions,id,company_id,'.$user->company()->id;
$rules['invoice_id'] = 'bail|sometimes|nullable|exists:invoices,id,company_id,'.auth()->user()->company()->id; $rules['invoice_id'] = 'bail|sometimes|nullable|exists:invoices,id,company_id,'.$user->company()->id;
$rules['documents'] = 'bail|sometimes|array';
return $this->globalRules($rules); return $this->globalRules($rules);
@ -55,6 +62,10 @@ class UpdateExpenseRequest extends Request
public function prepareForValidation() public function prepareForValidation()
{ {
/** @var \App\Models\User $user */
$user = auth()->user();
$input = $this->all(); $input = $this->all();
$input = $this->decodePrimaryKeys($input); $input = $this->decodePrimaryKeys($input);
@ -64,7 +75,7 @@ class UpdateExpenseRequest extends Request
} }
if (! array_key_exists('currency_id', $input) || strlen($input['currency_id']) == 0) { if (! array_key_exists('currency_id', $input) || strlen($input['currency_id']) == 0) {
$input['currency_id'] = (string) auth()->user()->company()->settings->currency_id; $input['currency_id'] = (string) $user->company()->settings->currency_id;
} }
/* Ensure the project is related */ /* Ensure the project is related */

View File

@ -11,6 +11,7 @@
namespace App\Http\Requests\ExpenseCategory; namespace App\Http\Requests\ExpenseCategory;
use App\Models\Expense;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use App\Models\ExpenseCategory; use App\Models\ExpenseCategory;
@ -23,14 +24,21 @@ class StoreExpenseCategoryRequest extends Request
*/ */
public function authorize(): bool public function authorize(): bool
{ {
return auth()->user()->can('create', ExpenseCategory::class); /** @var \App\Models\User $user */
$user = auth()->user();
return $user->can('create', ExpenseCategory::class) || $user->can('create', Expense::class);
} }
public function rules() public function rules()
{ {
/** @var \App\Models\User $user */
$user = auth()->user();
$rules = []; $rules = [];
$rules['name'] = 'required|unique:expense_categories,name,null,null,company_id,'.auth()->user()->companyId(); $rules['name'] = 'required|unique:expense_categories,name,null,null,company_id,'.$user->companyId();
return $this->globalRules($rules); return $this->globalRules($rules);
} }

View File

@ -26,16 +26,24 @@ class UpdateExpenseCategoryRequest extends Request
*/ */
public function authorize(): bool public function authorize(): bool
{ {
return auth()->user()->can('edit', $this->expense_category);
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->can('edit', $this->expense_category);
} }
public function rules() public function rules()
{ {
/** @var \App\Models\User $user */
$user = auth()->user();
$rules = []; $rules = [];
if ($this->input('name')) { if ($this->input('name')) {
// $rules['name'] = 'unique:expense_categories,name,'.$this->id.',id,company_id,'.$this->expense_category->company_id; // $rules['name'] = 'unique:expense_categories,name,'.$this->id.',id,company_id,'.$this->expense_category->company_id;
$rules['name'] = Rule::unique('expense_categories')->where('company_id', auth()->user()->company()->id)->ignore($this->expense_category->id); $rules['name'] = Rule::unique('expense_categories')->where('company_id', $user->company()->id)->ignore($this->expense_category->id);
} }
return $rules; return $rules;

View File

@ -47,6 +47,8 @@ class StoreInvoiceRequest extends Request
$rules['documents.*'] = $this->file_validation; $rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) { } elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation; $rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
} }
if ($this->file('file') && is_array($this->file('file'))) { if ($this->file('file') && is_array($this->file('file'))) {
@ -76,6 +78,7 @@ class StoreInvoiceRequest extends Request
$rules['partial'] = 'bail|sometimes|nullable|numeric|gte:0'; $rules['partial'] = 'bail|sometimes|nullable|numeric|gte:0';
$rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date']; $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date'];
return $rules; return $rules;
} }

View File

@ -49,6 +49,8 @@ class UpdateInvoiceRequest extends Request
$rules['documents.*'] = $this->file_validation; $rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) { } elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation; $rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
} }
if ($this->file('file') && is_array($this->file('file'))) { if ($this->file('file') && is_array($this->file('file'))) {
@ -77,6 +79,7 @@ class UpdateInvoiceRequest extends Request
$rules['partial'] = 'bail|sometimes|nullable|numeric'; $rules['partial'] = 'bail|sometimes|nullable|numeric';
$rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date']; $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date'];
return $rules; return $rules;
} }

View File

@ -126,6 +126,8 @@ class StorePaymentRequest extends Request
$rules['documents.*'] = $this->file_validation; $rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) { } elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation; $rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
} }
if ($this->file('file') && is_array($this->file('file'))) { if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -55,6 +55,8 @@ class UpdatePaymentRequest extends Request
$rules['documents.*'] = $this->file_validation; $rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) { } elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation; $rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
} }
if ($this->file('file') && is_array($this->file('file'))) { if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -35,6 +35,8 @@ class StoreProductRequest extends Request
$rules['documents.*'] = $this->file_validation; $rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) { } elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation; $rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
} }
if ($this->file('file') && is_array($this->file('file'))) { if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -25,7 +25,11 @@ class UpdateProductRequest extends Request
*/ */
public function authorize(): bool public function authorize(): bool
{ {
return auth()->user()->can('edit', $this->product);
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->can('edit', $this->product);
} }
public function rules() public function rules()
@ -34,6 +38,8 @@ class UpdateProductRequest extends Request
$rules['documents.*'] = $this->file_validation; $rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) { } elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation; $rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
} }
if ($this->file('file') && is_array($this->file('file'))) { if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -53,6 +53,8 @@ class StoreProjectRequest extends Request
$rules['documents.*'] = $this->file_validation; $rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) { } elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation; $rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
} }
if ($this->file('file') && is_array($this->file('file'))) { if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -49,6 +49,8 @@ class UpdateProjectRequest extends Request
$rules['documents.*'] = $this->file_validation; $rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) { } elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation; $rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
} }
if ($this->file('file') && is_array($this->file('file'))) { if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -57,6 +57,8 @@ class StorePurchaseOrderRequest extends Request
$rules['documents.*'] = $this->file_validation; $rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) { } elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation; $rules['documents'] = $this->file_validation;
} else {
$rules['documents'] = 'bail|sometimes|array';
} }
if ($this->file('file') && is_array($this->file('file'))) { if ($this->file('file') && is_array($this->file('file'))) {

Some files were not shown because too many files have changed in this diff Show More