mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Merge branch 'v5-develop' of https://github.com/invoiceninja/invoiceninja into feature-brevo
This commit is contained in:
commit
4e180db731
2
.github/workflows/phpunit.yml
vendored
2
.github/workflows/phpunit.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
phpunit-versions: ['latest']
|
||||
ci_node_total: [ 8 ]
|
||||
ci_node_index: [ 0, 1, 2, 3, 4, 5, 6, 7]
|
||||
laravel: [9.*]
|
||||
laravel: [10.*]
|
||||
dependency-version: [prefer-stable]
|
||||
|
||||
env:
|
||||
|
76
.github/workflows/react_release.yml
vendored
Normal file
76
.github/workflows/react_release.yml
vendored
Normal 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
|
@ -1 +1 @@
|
||||
5.8.12
|
||||
5.8.26
|
@ -890,6 +890,66 @@ class CheckData extends Command
|
||||
|
||||
$this->logMessage("Fixing country for # {$client->id}");
|
||||
});
|
||||
|
||||
Client::query()->whereNull("settings->currency_id")->cursor()->each(function ($client) {
|
||||
$settings = $client->settings;
|
||||
$settings->currency_id = (string)$client->company->settings->currency_id;
|
||||
$client->settings = $settings;
|
||||
$client->saveQuietly();
|
||||
|
||||
$this->logMessage("Fixing currency_id for # {$client->id}");
|
||||
|
||||
});
|
||||
|
||||
Payment::withTrashed()->where('exchange_rate', 0)->cursor()->each(function ($payment) {
|
||||
$payment->exchange_rate = 1;
|
||||
$payment->saveQuietly();
|
||||
|
||||
$this->logMessage("Fixing exchange rate for # {$payment->id}");
|
||||
});
|
||||
|
||||
Payment::withTrashed()
|
||||
->whereHas("client", function ($query) {
|
||||
$query->whereColumn("settings->currency_id", "!=", "payments.currency_id");
|
||||
})
|
||||
->cursor()
|
||||
->each(function ($p) {
|
||||
$p->currency_id = $p->client->settings->currency_id;
|
||||
$p->saveQuietly();
|
||||
|
||||
|
||||
$this->logMessage("Fixing currency for # {$p->id}");
|
||||
|
||||
});
|
||||
|
||||
Company::whereNull("subdomain")
|
||||
->cursor()
|
||||
->when(Ninja::isHosted())
|
||||
->each(function ($c) {
|
||||
$c->subdomain = MultiDB::randomSubdomainGenerator();
|
||||
$c->save();
|
||||
|
||||
$this->logMessage("Fixing subdomain for # {$c->id}");
|
||||
|
||||
});
|
||||
|
||||
|
||||
Invoice::withTrashed()
|
||||
->where("partial", 0)
|
||||
->whereNotNull("partial_due_date")
|
||||
->cursor()
|
||||
->each(function ($i) {
|
||||
$i->partial_due_date = null;
|
||||
$i->saveQuietly();
|
||||
|
||||
|
||||
$this->logMessage("Fixing partial due date for # {$i->id}");
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
84
app/Console/Commands/EncryptNinja.php
Normal file
84
app/Console/Commands/EncryptNinja.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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/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/responses.yaml'));
|
||||
|
||||
$directory = new DirectoryIterator($path . '/components/responses/');
|
||||
|
||||
foreach ($directory as $file) {
|
||||
|
@ -495,7 +495,13 @@ class CompanySettings extends BaseSettings
|
||||
|
||||
public $payment_email_all_contacts = false;
|
||||
|
||||
public $show_pdfhtml_on_mobile = true;
|
||||
|
||||
public $use_unapplied_payment = 'off'; //always, option, off //@implemented
|
||||
|
||||
public static $casts = [
|
||||
'use_unapplied_payment' => 'string',
|
||||
'show_pdfhtml_on_mobile' => 'bool',
|
||||
'payment_email_all_contacts' => 'bool',
|
||||
'statement_design_id' => 'string',
|
||||
'delivery_note_design_id' => 'string',
|
||||
@ -878,7 +884,7 @@ class CompanySettings extends BaseSettings
|
||||
{
|
||||
$notification = new stdClass();
|
||||
$notification->email = [];
|
||||
$notification->email = ['invoice_sent_all'];
|
||||
$notification->email = ['invoice_sent_all', 'payment_success_all', 'payment_manual_all'];
|
||||
|
||||
return $notification;
|
||||
}
|
||||
|
@ -319,6 +319,7 @@ class BaseRule implements RuleInterface
|
||||
Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt($item),
|
||||
Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced($item),
|
||||
Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override($item),
|
||||
Product::PRODUCT_TYPE_ZERO_RATED => $this->zeroRated($item),
|
||||
default => $this->defaultForeign(),
|
||||
};
|
||||
|
||||
@ -327,6 +328,14 @@ class BaseRule implements RuleInterface
|
||||
|
||||
}
|
||||
|
||||
public function zeroRated($item): self
|
||||
{
|
||||
$this->tax_rate1 = 0;
|
||||
$this->tax_name1 = ctrans('texts.zero_rated');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function taxByType(mixed $type): self
|
||||
{
|
||||
return $this;
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\DataMapper\Tax\DE;
|
||||
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\DataMapper\Tax\BaseRule;
|
||||
use App\DataMapper\Tax\RuleInterface;
|
||||
use App\Models\Product;
|
||||
@ -63,7 +64,7 @@ class Rule extends BaseRule implements RuleInterface
|
||||
public function taxByType($item): self
|
||||
{
|
||||
|
||||
if ($this->client->is_tax_exempt || !property_exists($item, 'tax_id')) {
|
||||
if ($this->client->is_tax_exempt || !property_exists($item, 'tax_id') || (isset($item->type_id) && $item->type_id == '5')) {
|
||||
return $this->taxExempt($item);
|
||||
}
|
||||
|
||||
|
@ -59391,7 +59391,7 @@ class Domains
|
||||
'wireconnected.com'
|
||||
];
|
||||
|
||||
public static function getDomains()
|
||||
public static function getDomains(): array
|
||||
{
|
||||
return self::$verify_domains;
|
||||
}
|
||||
|
@ -104,10 +104,10 @@ class Handler extends ExceptionHandler
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
|
||||
if($exception instanceof ThrottleRequestsException && class_exists(\Modules\Admin\Events\ThrottledExceptionRaised::class)) {
|
||||
$uri = urldecode(request()->getRequestUri());
|
||||
// event(new \Modules\Admin\Events\ThrottledExceptionRaised(auth()->user()?->account?->key, $uri, request()->ip()));
|
||||
}
|
||||
// if($exception instanceof ThrottleRequestsException && class_exists(\Modules\Admin\Events\ThrottledExceptionRaised::class)) {
|
||||
// $uri = urldecode(request()->getRequestUri());
|
||||
// event(new \Modules\Admin\Events\ThrottledExceptionRaised(auth()->user()?->account?->key, $uri, request()->ip()));
|
||||
// }
|
||||
|
||||
Integration::configureScope(function (Scope $scope): void {
|
||||
$name = 'hosted@invoiceninja.com';
|
||||
|
@ -11,27 +11,29 @@
|
||||
|
||||
namespace App\Export\CSV;
|
||||
|
||||
use App\Models\Task;
|
||||
use App\Models\User;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Document;
|
||||
use App\Models\Vendor;
|
||||
use App\Utils\Helpers;
|
||||
use App\Models\Company;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Product;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\Task;
|
||||
use App\Models\Vendor;
|
||||
use App\Transformers\PaymentTransformer;
|
||||
use App\Transformers\TaskTransformer;
|
||||
use App\Utils\Helpers;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use App\Models\Document;
|
||||
use League\Fractal\Manager;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\PurchaseOrder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Jobs\Document\ZipDocuments;
|
||||
use App\Transformers\TaskTransformer;
|
||||
use App\Transformers\PaymentTransformer;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use League\Fractal\Serializer\ArraySerializer;
|
||||
|
||||
class BaseExport
|
||||
@ -387,6 +389,8 @@ class BaseExport
|
||||
|
||||
protected array $expense_report_keys = [
|
||||
'amount' => 'expense.amount',
|
||||
'tax_amount' => 'expense.tax_amount',
|
||||
'net_amount' => 'expense.net_amount',
|
||||
'category' => 'expense.category_id',
|
||||
// 'client' => 'expense.client_id',
|
||||
'custom_value1' => 'expense.custom_value1',
|
||||
@ -446,9 +450,20 @@ class BaseExport
|
||||
protected function filterByClients($query)
|
||||
{
|
||||
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']);
|
||||
|
||||
if(!$client) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$this->client_description = $client->present()->name;
|
||||
return $query->where('client_id', $this->input['client_id']);
|
||||
|
||||
} elseif(isset($this->input['clients']) && count($this->input['clients']) > 0) {
|
||||
|
||||
$this->client_description = 'Multiple Clients';
|
||||
@ -830,12 +845,78 @@ class BaseExport
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function addClientFilter($query, $clients): Builder
|
||||
{
|
||||
if(is_string($clients)) {
|
||||
$clients = explode(',', $clients);
|
||||
}
|
||||
|
||||
$transformed_clients = $this->transformKeys($clients);
|
||||
|
||||
nlog($clients);
|
||||
nlog($transformed_clients);
|
||||
|
||||
if(count($transformed_clients) > 0) {
|
||||
$query->whereIn('client_id', $transformed_clients);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function addVendorFilter($query, $vendors): Builder
|
||||
{
|
||||
|
||||
if(is_string($vendors)) {
|
||||
$vendors = explode(',', $vendors);
|
||||
}
|
||||
|
||||
$transformed_vendors = $this->transformKeys($vendors);
|
||||
|
||||
if(count($transformed_vendors) > 0) {
|
||||
$query->whereIn('vendor_id', $transformed_vendors);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function addProjectFilter($query, $projects): Builder
|
||||
{
|
||||
|
||||
if(is_string($projects)) {
|
||||
$projects = explode(',', $projects);
|
||||
}
|
||||
|
||||
$transformed_projects = $this->transformKeys($projects);
|
||||
|
||||
if(count($transformed_projects) > 0) {
|
||||
$query->whereIn('project_id', $transformed_projects);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function addCategoryFilter($query, $expense_categories): Builder
|
||||
{
|
||||
|
||||
if(is_string($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;
|
||||
}
|
||||
|
||||
protected function addInvoiceStatusFilter($query, $status): Builder
|
||||
{
|
||||
|
||||
$status_parameters = explode(',', $status);
|
||||
|
||||
|
||||
if(in_array('all', $status_parameters)) {
|
||||
return $query;
|
||||
}
|
||||
@ -891,6 +972,8 @@ class BaseExport
|
||||
|
||||
$date_range = $this->input['date_range'];
|
||||
|
||||
nlog($date_range);
|
||||
|
||||
if (array_key_exists('date_key', $this->input) && strlen($this->input['date_key']) > 1) {
|
||||
$this->date_key = $this->input['date_key'];
|
||||
}
|
||||
@ -1220,4 +1303,35 @@ class BaseExport
|
||||
return $clean_row;
|
||||
}
|
||||
|
||||
public function queueDocuments(Builder $query)
|
||||
{
|
||||
nlog("queue docs pls");
|
||||
if($query->getModel() instanceof Document) {
|
||||
$documents = $query->pluck('id')->toArray();
|
||||
} else {
|
||||
$documents = $query->cursor()
|
||||
->map(function ($entity) {
|
||||
return $entity->documents()->pluck('id')->toArray();
|
||||
})->flatten()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
nlog($documents);
|
||||
|
||||
if(count($documents) > 0) {
|
||||
|
||||
$user = $this->company->owner();
|
||||
|
||||
if(auth()->user() && auth()->user()->account_id == $this->company->account_id) {
|
||||
$user = auth()->user();
|
||||
}
|
||||
|
||||
if($this->input['user_id'] ?? false) {
|
||||
$user = User::where('id', $this->input['user_id'])->where('account_id', $this->company->account_id)->first();
|
||||
}
|
||||
|
||||
ZipDocuments::dispatch($documents, $this->company, $user);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -131,6 +131,10 @@ class ClientExport extends BaseExport
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
|
||||
}
|
||||
@ -221,22 +225,6 @@ class ClientExport extends BaseExport
|
||||
$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)) {
|
||||
$entity['client.classification'] = ctrans("texts.{$client->classification}") ?? '';
|
||||
}
|
||||
|
@ -107,6 +107,10 @@ class CreditExport extends BaseExport
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
@ -78,6 +78,10 @@ class DocumentExport extends BaseExport
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
|
||||
}
|
||||
|
@ -72,6 +72,13 @@ class ExpenseExport extends BaseExport
|
||||
$this->input['report_keys'] = array_values($this->expense_report_keys);
|
||||
}
|
||||
|
||||
$tax_keys = [
|
||||
'expense.tax_amount',
|
||||
'expense.net_amount'
|
||||
];
|
||||
|
||||
$this->input['report_keys'] = array_unique(array_merge($this->input['report_keys'], $tax_keys));
|
||||
|
||||
$query = Expense::query()
|
||||
->with('client')
|
||||
->withTrashed()
|
||||
@ -80,6 +87,26 @@ class ExpenseExport extends BaseExport
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
if(isset($this->input['clients'])) {
|
||||
$query = $this->addClientFilter($query, $this->input['clients']);
|
||||
}
|
||||
|
||||
if(isset($this->input['vendors'])) {
|
||||
$query = $this->addVendorFilter($query, $this->input['vendors']);
|
||||
}
|
||||
|
||||
if(isset($this->input['projects'])) {
|
||||
$query = $this->addProjectFilter($query, $this->input['projects']);
|
||||
}
|
||||
|
||||
if(isset($this->input['categories'])) {
|
||||
$query = $this->addCategoryFilter($query, $this->input['categories']);
|
||||
}
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
|
||||
}
|
||||
@ -117,15 +144,11 @@ class ExpenseExport extends BaseExport
|
||||
} elseif (array_key_exists($key, $transformed_expense)) {
|
||||
$entity[$key] = $transformed_expense[$key];
|
||||
} else {
|
||||
// nlog($key);
|
||||
$entity[$key] = $this->decorator->transform($key, $expense);
|
||||
// $entity[$key] = '';
|
||||
// $entity[$key] = $this->resolveKey($key, $expense, $this->expense_transformer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// return $entity;
|
||||
return $this->decorateAdvancedFields($expense, $entity);
|
||||
}
|
||||
|
||||
@ -171,6 +194,36 @@ class ExpenseExport extends BaseExport
|
||||
$entity['expense.category_id'] = $expense->category ? $expense->category->name : '';
|
||||
}
|
||||
|
||||
return $this->calcTaxes($entity, $expense);
|
||||
}
|
||||
|
||||
private function calcTaxes($entity, $expense): array
|
||||
{
|
||||
$precision = $expense->currency->precision ?? 2;
|
||||
|
||||
$entity['expense.net_amount'] = round($expense->amount, $precision);
|
||||
|
||||
if($expense->calculate_tax_by_amount) {
|
||||
$total_tax_amount = round($expense->tax_amount1 + $expense->tax_amount2 + $expense->tax_amount3, $precision);
|
||||
} else {
|
||||
|
||||
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));
|
||||
$entity['expense.net_amount'] = round(($expense->amount - round($total_tax_amount, $precision)), $precision);
|
||||
} 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));
|
||||
$entity['expense.net_amount'] = round(($expense->amount + round($total_tax_amount, $precision)), $precision);
|
||||
}
|
||||
}
|
||||
|
||||
$entity['expense.tax_amount'] = round($total_tax_amount, $precision);
|
||||
|
||||
return $entity;
|
||||
|
||||
}
|
||||
|
||||
private function calcInclusiveLineTax($tax_rate, $amount, $precision): float
|
||||
{
|
||||
return round($amount - ($amount / (1 + ($tax_rate / 100))), $precision);
|
||||
}
|
||||
}
|
||||
|
@ -62,10 +62,14 @@ class InvoiceExport extends BaseExport
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
if(isset($this->input['status'])) {
|
||||
if($this->input['status'] ?? false) {
|
||||
$query = $this->addInvoiceStatusFilter($query, $this->input['status']);
|
||||
}
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
|
||||
}
|
||||
@ -120,15 +124,11 @@ class InvoiceExport extends BaseExport
|
||||
if (is_array($parts) && $parts[0] == 'invoice' && array_key_exists($parts[1], $transformed_invoice)) {
|
||||
$entity[$key] = $transformed_invoice[$parts[1]];
|
||||
} else {
|
||||
// nlog($key);
|
||||
$entity[$key] = $this->decorator->transform($key, $invoice);
|
||||
// $entity[$key] = '';
|
||||
// $entity[$key] = $this->resolveKey($key, $invoice, $this->invoice_transformer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// return $entity;
|
||||
return $this->decorateAdvancedFields($invoice, $entity);
|
||||
}
|
||||
|
||||
@ -167,7 +167,6 @@ class InvoiceExport extends BaseExport
|
||||
$entity['invoice.user_id'] = $invoice->user ? $invoice->user->present()->name() : '';
|
||||
}
|
||||
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
||||
|
@ -77,6 +77,10 @@ class InvoiceItemExport extends BaseExport
|
||||
|
||||
$query = $this->applyFilters($query);
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
|
||||
}
|
||||
@ -91,7 +95,6 @@ class InvoiceItemExport extends BaseExport
|
||||
return ['identifier' => $key, 'display_value' => $headerdisplay[$value]];
|
||||
})->toArray();
|
||||
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($resource) {
|
||||
$this->iterateItems($resource);
|
||||
|
@ -61,6 +61,10 @@ class PaymentExport extends BaseExport
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
@ -78,6 +78,10 @@ class ProductExport extends BaseExport
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
|
||||
}
|
||||
|
@ -108,6 +108,10 @@ class PurchaseOrderExport extends BaseExport
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
|
||||
}
|
||||
|
@ -67,6 +67,10 @@ class PurchaseOrderItemExport extends BaseExport
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
|
||||
}
|
||||
|
@ -69,6 +69,10 @@ class QuoteExport extends BaseExport
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
|
||||
}
|
||||
|
@ -70,6 +70,10 @@ class QuoteItemExport extends BaseExport
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
|
||||
}
|
||||
|
@ -72,6 +72,10 @@ class TaskExport extends BaseExport
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
|
||||
}
|
||||
|
@ -66,6 +66,10 @@ class VendorExport extends BaseExport
|
||||
|
||||
$query = $this->addDateRange($query);
|
||||
|
||||
if($this->input['document_email_attachment'] ?? false) {
|
||||
$this->queueDocuments($query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ class ClientDecorator extends Decorator implements DecoratorInterface
|
||||
|
||||
if($client && method_exists($this, $key)) {
|
||||
return $this->{$key}($client);
|
||||
} elseif($client && $client->{$key}) {
|
||||
} elseif($client && ($client->{$key} ?? false)) {
|
||||
return $client->{$key};
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ class ContactDecorator implements DecoratorInterface
|
||||
|
||||
if($contact && method_exists($this, $key)) {
|
||||
return $this->{$key}($contact);
|
||||
} elseif($contact && $contact->{$key}) {
|
||||
} elseif($contact && ($contact->{$key} ?? false)) {
|
||||
return $contact->{$key};
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ class CreditDecorator implements DecoratorInterface
|
||||
|
||||
if($credit && method_exists($this, $key)) {
|
||||
return $this->{$key}($credit);
|
||||
} elseif($credit && $credit->{$key}) {
|
||||
} elseif($credit && ($credit->{$key} ?? false)) {
|
||||
return $credit->{$key};
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ class ExpenseDecorator implements DecoratorInterface
|
||||
|
||||
if($expense && method_exists($this, $key)) {
|
||||
return $this->{$key}($expense);
|
||||
} elseif($expense && $expense->{$key}) {
|
||||
} elseif($expense && ($expense->{$key} ?? false)) {
|
||||
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)
|
||||
{
|
||||
return $expense->category ? $expense->category->name : '';
|
||||
|
@ -29,7 +29,7 @@ class InvoiceDecorator extends Decorator implements DecoratorInterface
|
||||
|
||||
if($invoice && method_exists($this, $key)) {
|
||||
return $this->{$key}($invoice);
|
||||
} elseif($invoice && $invoice->{$key}) {
|
||||
} elseif($invoice && ($invoice->{$key} ?? false)) {
|
||||
return $invoice->{$key};
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ class PaymentDecorator extends Decorator implements DecoratorInterface
|
||||
|
||||
if($payment && method_exists($this, $key)) {
|
||||
return $this->{$key}($payment);
|
||||
} elseif($payment && $payment->{$key}) {
|
||||
} elseif($payment && ($payment->{$key} ?? false)) {
|
||||
return $payment->{$key};
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ class ProductDecorator implements DecoratorInterface
|
||||
|
||||
if($product && method_exists($this, $key)) {
|
||||
return $this->{$key}($product);
|
||||
} elseif($product->{$key}) {
|
||||
} elseif($product->{$key} ?? false) {
|
||||
return $product->{$key} ?? '';
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ class PurchaseOrderDecorator extends Decorator implements DecoratorInterface
|
||||
|
||||
if($purchase_order && method_exists($this, $key)) {
|
||||
return $this->{$key}($purchase_order);
|
||||
} elseif($purchase_order->{$key}) {
|
||||
} elseif($purchase_order->{$key} ?? false) {
|
||||
return $purchase_order->{$key} ?? '';
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ class QuoteDecorator extends Decorator implements DecoratorInterface
|
||||
|
||||
if($quote && method_exists($this, $key)) {
|
||||
return $this->{$key}($quote);
|
||||
} elseif($quote->{$key}) {
|
||||
} elseif($quote->{$key} ?? false) {
|
||||
return $quote->{$key} ?? '';
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ class RecurringInvoiceDecorator extends Decorator implements DecoratorInterface
|
||||
|
||||
if($recurring_invoice && method_exists($this, $key)) {
|
||||
return $this->{$key}($recurring_invoice);
|
||||
} elseif($recurring_invoice->{$key}) {
|
||||
} elseif($recurring_invoice->{$key} ?? false) {
|
||||
return $recurring_invoice->{$key} ?? '';
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ class TaskDecorator extends Decorator implements DecoratorInterface
|
||||
|
||||
if($task && method_exists($this, $key)) {
|
||||
return $this->{$key}($task);
|
||||
} elseif($task && $task->{$key}) {
|
||||
} elseif($task && $task->{$key} ?? false) {
|
||||
return $task->{$key};
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ class VendorContactDecorator implements DecoratorInterface
|
||||
|
||||
if($contact && method_exists($this, $key)) {
|
||||
return $this->{$key}($contact);
|
||||
} elseif($contact && $contact->{$key}) {
|
||||
} elseif($contact && ($contact->{$key} ?? false)) {
|
||||
return $contact->{$key} ?? '';
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ class VendorDecorator extends Decorator implements DecoratorInterface
|
||||
|
||||
if($vendor && method_exists($this, $key)) {
|
||||
return $this->{$key}($vendor);
|
||||
} elseif($vendor->{$key}) {
|
||||
} elseif($vendor->{$key} ?? false) {
|
||||
return $vendor->{$key} ?? '';
|
||||
}
|
||||
|
||||
|
@ -49,6 +49,14 @@ class CompanyFactory
|
||||
$company->markdown_enabled = false;
|
||||
$company->tax_data = new TaxModel();
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
@ -165,6 +165,10 @@ class ClientFilters extends QueryFilters
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
|
@ -146,6 +146,11 @@ class CreditFilters extends QueryFilters
|
||||
->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);
|
||||
}
|
||||
|
||||
|
@ -172,6 +172,8 @@ class ExpenseFilters extends QueryFilters
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
$dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
|
||||
|
||||
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])
|
||||
@ -194,6 +196,10 @@ class ExpenseFilters extends QueryFilters
|
||||
->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]);
|
||||
}
|
||||
|
@ -247,24 +247,25 @@ class InvoiceFilters extends QueryFilters
|
||||
return $this->builder->where('due_date', '>=', $date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by date range
|
||||
*
|
||||
* @param string $date_range
|
||||
* @return Builder
|
||||
*/
|
||||
public function date_range(string $date_range = ''): Builder
|
||||
{
|
||||
$parts = explode(",", $date_range);
|
||||
|
||||
if (count($parts) != 3) {
|
||||
if (count($parts) != 2) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
if(!in_array($parts[0], ['date','due_date'])) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
$start_date = Carbon::parse($parts[1]);
|
||||
$end_date = Carbon::parse($parts[2]);
|
||||
$start_date = Carbon::parse($parts[0]);
|
||||
$end_date = Carbon::parse($parts[1]);
|
||||
|
||||
return $this->builder->whereBetween($parts[0], [$start_date, $end_date]);
|
||||
return $this->builder->whereBetween('date', [$start_date, $end_date]);
|
||||
} catch(\Exception $e) {
|
||||
return $this->builder;
|
||||
}
|
||||
@ -272,6 +273,33 @@ class InvoiceFilters extends QueryFilters
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by due date range
|
||||
*
|
||||
* @param string $date_range
|
||||
* @return Builder
|
||||
*/
|
||||
public function due_date_range(string $date_range = ''): Builder
|
||||
{
|
||||
$parts = explode(",", $date_range);
|
||||
|
||||
if (count($parts) != 2) {
|
||||
return $this->builder;
|
||||
}
|
||||
try {
|
||||
|
||||
$start_date = Carbon::parse($parts[0]);
|
||||
$end_date = Carbon::parse($parts[1]);
|
||||
|
||||
return $this->builder->whereBetween('due_date', [$start_date, $end_date]);
|
||||
} catch(\Exception $e) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sorts the list based on $sort.
|
||||
*
|
||||
@ -295,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);
|
||||
}
|
||||
|
||||
|
@ -12,8 +12,9 @@
|
||||
namespace App\Filters;
|
||||
|
||||
use App\Models\Payment;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* PaymentFilters.
|
||||
@ -163,18 +164,22 @@ class PaymentFilters extends QueryFilters
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
$dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
|
||||
|
||||
if ($sort_col[0] == 'client_id') {
|
||||
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
|
||||
|
@ -60,20 +60,23 @@ class ProjectFilters extends QueryFilters
|
||||
{
|
||||
$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) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
if (is_array($sort_col) && in_array($sort_col[1], ['asc','desc'])) {
|
||||
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
|
||||
$dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,6 +130,10 @@ class PurchaseOrderFilters extends QueryFilters
|
||||
->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);
|
||||
}
|
||||
|
||||
|
@ -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') {
|
||||
$sort_col[0] = 'due_date';
|
||||
}
|
||||
|
@ -31,13 +31,22 @@ class RecurringExpenseFilters extends QueryFilters
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder->where(function ($query) use ($filter) {
|
||||
$query->where('public_notes', 'like', '%'.$filter.'%')
|
||||
->orWhere('custom_value1', 'like', '%'.$filter.'%')
|
||||
->orWhere('custom_value2', 'like', '%'.$filter.'%')
|
||||
->orWhere('custom_value3', 'like', '%'.$filter.'%')
|
||||
->orWhere('custom_value4', 'like', '%'.$filter.'%');
|
||||
return $this->builder->where(function ($query) use ($filter) {
|
||||
$query->where('number', 'like', '%' . $filter . '%')
|
||||
->orWhere('amount', 'like', '%' . $filter . '%')
|
||||
->orWhere('public_notes', 'like', '%' . $filter . '%')
|
||||
->orWhere('custom_value1', '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
|
||||
@ -49,6 +58,74 @@ class RecurringExpenseFilters extends QueryFilters
|
||||
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.
|
||||
*
|
||||
@ -65,7 +142,37 @@ class RecurringExpenseFilters extends QueryFilters
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,6 +143,10 @@ class TaskFilters extends QueryFilters
|
||||
->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);
|
||||
}
|
||||
|
||||
|
@ -71,6 +71,10 @@ class VendorFilters extends QueryFilters
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
|
@ -104,14 +104,14 @@ class AccountTransformer implements AccountTransformerInterface
|
||||
return [
|
||||
'id' => $nordigen_account->metadata["id"],
|
||||
'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_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_id' => $nordigen_account->institution["id"],
|
||||
'provider_name' => $nordigen_account->institution["name"],
|
||||
'nickname' => $nordigen_account->data["ownerName"] ? $nordigen_account->data["ownerName"] : '',
|
||||
'current_balance' => (int) $used_balance ? $used_balance["balanceAmount"]["amount"] : 0,
|
||||
'nickname' => isset($nordigen_account->data["ownerName"]) ? $nordigen_account->data["ownerName"] : '',
|
||||
'current_balance' => (float) $used_balance ? $used_balance["balanceAmount"]["amount"] : 0,
|
||||
'account_currency' => $used_balance ? $used_balance["balanceAmount"]["currency"] : '',
|
||||
];
|
||||
|
||||
|
@ -70,8 +70,9 @@ class TransactionTransformer implements BankRevenueInterface
|
||||
{
|
||||
$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');
|
||||
}
|
||||
|
||||
foreach ($transactionResponse["transactions"]["booked"] as $transaction) {
|
||||
$data[] = $this->transformTransaction($transaction);
|
||||
@ -81,31 +82,40 @@ class TransactionTransformer implements BankRevenueInterface
|
||||
|
||||
public function transformTransaction($transaction)
|
||||
{
|
||||
|
||||
if (!array_key_exists('transactionId', $transaction) || !array_key_exists('transactionAmount', $transaction))
|
||||
throw new \Exception('invalid dataset');
|
||||
// depending on institution, the result can be different, so we load the first available unique id
|
||||
$transactionId = '';
|
||||
if (array_key_exists('transactionId', $transaction)) {
|
||||
$transactionId = $transaction["transactionId"];
|
||||
} elseif (array_key_exists('internalTransactionId', $transaction)) {
|
||||
$transactionId = $transaction["internalTransactionId"];
|
||||
} else {
|
||||
nlog(`Invalid Input for nordigen transaction transformer: ` . $transaction);
|
||||
throw new \Exception('invalid dataset: missing transactionId - Please report this error to the developer');
|
||||
}
|
||||
|
||||
$amount = (float) $transaction["transactionAmount"]["amount"];
|
||||
|
||||
// description could be in varios places
|
||||
$description = '';
|
||||
if (array_key_exists('remittanceInformationStructured', $transaction))
|
||||
if (array_key_exists('remittanceInformationStructured', $transaction)) {
|
||||
$description = $transaction["remittanceInformationStructured"];
|
||||
else if (array_key_exists('remittanceInformationStructuredArray', $transaction))
|
||||
} elseif (array_key_exists('remittanceInformationStructuredArray', $transaction)) {
|
||||
$description = implode('\n', $transaction["remittanceInformationStructuredArray"]);
|
||||
else if (array_key_exists('remittanceInformationUnstructured', $transaction))
|
||||
} elseif (array_key_exists('remittanceInformationUnstructured', $transaction)) {
|
||||
$description = $transaction["remittanceInformationUnstructured"];
|
||||
else if (array_key_exists('remittanceInformationUnstructuredArray', $transaction))
|
||||
} elseif (array_key_exists('remittanceInformationUnstructuredArray', $transaction)) {
|
||||
$description = implode('\n', $transaction["remittanceInformationUnstructuredArray"]);
|
||||
else
|
||||
} else {
|
||||
Log::warning("Missing description for the following transaction: " . json_encode($transaction));
|
||||
}
|
||||
|
||||
// enrich description with currencyExchange informations
|
||||
if (array_key_exists('currencyExchange', $transaction))
|
||||
if (isset($transaction['currencyExchange'])) {
|
||||
foreach ($transaction["currencyExchange"] as $exchangeRate) {
|
||||
$targetAmount = round($amount * (float) $exchangeRate["exchangeRate"], 2);
|
||||
$description .= '\nexchangeRate: ' . $amount . " " . $exchangeRate["sourceCurrency"] . " = " . $targetAmount . " " . $exchangeRate["targetCurrency"] . " (" . $exchangeRate["quotationDate"] . ")";
|
||||
$targetAmount = round($amount * (float) ($exchangeRate["exchangeRate"] ?? 1) , 2);
|
||||
$description .= '\nexchangeRate: ' . $amount . " " . ($exchangeRate["sourceCurrency"] ?? '?') . " = " . $targetAmount . " " . ($exchangeRate["targetCurrency"] ?? '?') . " (" . ($exchangeRate["quotationDate"] ?? '?') . ")";
|
||||
}
|
||||
}
|
||||
|
||||
// participant data
|
||||
$participant = array_key_exists('debtorAccount', $transaction) && array_key_exists('iban', $transaction["debtorAccount"]) ?
|
||||
@ -118,7 +128,8 @@ class TransactionTransformer implements BankRevenueInterface
|
||||
$transaction['creditorName'] : null);
|
||||
|
||||
return [
|
||||
'transaction_id' => $transaction["transactionId"],
|
||||
'transaction_id' => 0,
|
||||
'nordigen_transaction_id' => $transactionId,
|
||||
'amount' => $amount,
|
||||
'currency_id' => $this->convertCurrency($transaction["transactionAmount"]["currency"]),
|
||||
'category_id' => null,
|
||||
@ -145,8 +156,9 @@ class TransactionTransformer implements BankRevenueInterface
|
||||
return $item->code == $code;
|
||||
})->first();
|
||||
|
||||
if ($currency)
|
||||
if ($currency) {
|
||||
return $currency->id;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
|
@ -44,6 +44,7 @@ class ActivityController extends BaseController
|
||||
{
|
||||
$default_activities = $request->has('rows') ? $request->input('rows') : 75;
|
||||
|
||||
/* @var App\Models\Activity[] $activities */
|
||||
$activities = Activity::with('user')
|
||||
->orderBy('created_at', 'DESC')
|
||||
->company()
|
||||
@ -62,6 +63,7 @@ class ActivityController extends BaseController
|
||||
|
||||
$data = $activities->cursor()->map(function ($activity) {
|
||||
|
||||
/** @var \App\Models\Activity $activity */
|
||||
return $activity->activity_string();
|
||||
|
||||
});
|
||||
@ -94,6 +96,7 @@ class ActivityController extends BaseController
|
||||
|
||||
$data = $activities->cursor()->map(function ($activity) {
|
||||
|
||||
/** @var \App\Models\Activity $activity */
|
||||
return $activity->activity_string();
|
||||
|
||||
});
|
||||
|
@ -99,22 +99,25 @@ class BankTransactionController extends BaseController
|
||||
|
||||
public function bulk(BulkBankTransactionRequest $request)
|
||||
{
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$action = $request->input('action');
|
||||
|
||||
$ids = request()->input('ids');
|
||||
|
||||
$bank_transactions = BankTransaction::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get();
|
||||
|
||||
if ($action == 'convert_matched') { //catch this action
|
||||
if ($action == 'convert_matched' && $user->can('edit', $bank_transactions->first())) { //catch this action
|
||||
$this->bank_transaction_repo->convert_matched($bank_transactions);
|
||||
} else {
|
||||
$bank_transactions->each(function ($bank_transaction, $key) use ($action) {
|
||||
$this->bank_transaction_repo->{$action}($bank_transaction);
|
||||
$bank_transactions->each(function ($bank_transaction, $key) use ($action, $user) {
|
||||
if($user->can('edit', $bank_transaction)) {
|
||||
$this->bank_transaction_repo->{$action}($bank_transaction);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Need to understand which permission are required for the given bulk action ie. view / edit */
|
||||
|
||||
return $this->listResponse(BankTransaction::withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
|
||||
}
|
||||
|
||||
|
@ -140,6 +140,7 @@ class BaseController extends Controller
|
||||
'company.quotes.invitations.company',
|
||||
'company.quotes.documents',
|
||||
'company.tasks.documents',
|
||||
// 'company.tasks.project',
|
||||
'company.subscriptions',
|
||||
'company.tax_rates',
|
||||
'company.tokens_hashed',
|
||||
@ -458,7 +459,7 @@ class BaseController extends Controller
|
||||
}
|
||||
},
|
||||
'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')) {
|
||||
$query->whereNested(function ($query) use ($user) {
|
||||
@ -796,7 +797,7 @@ class BaseController extends Controller
|
||||
}
|
||||
},
|
||||
'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')) {
|
||||
$query->whereNested(function ($query) use ($user) {
|
||||
|
@ -11,37 +11,46 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Events\Client\ClientWasCreated;
|
||||
use App\Events\Client\ClientWasUpdated;
|
||||
use App\Utils\Ninja;
|
||||
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\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\CreateClientRequest;
|
||||
use App\Http\Requests\Client\DestroyClientRequest;
|
||||
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\PurgeClientRequest;
|
||||
use App\Http\Requests\Client\StoreClientRequest;
|
||||
use App\Http\Requests\Client\CreateClientRequest;
|
||||
use App\Http\Requests\Client\UpdateClientRequest;
|
||||
use App\Http\Requests\Client\UploadClientRequest;
|
||||
use App\Jobs\Client\UpdateTaxData;
|
||||
use App\Jobs\PostMark\ProcessPostmarkWebhook;
|
||||
use App\Models\Account;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\SystemLog;
|
||||
use App\Repositories\ClientRepository;
|
||||
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;
|
||||
use App\Http\Requests\Client\DestroyClientRequest;
|
||||
use App\Http\Requests\Client\ClientDocumentsRequest;
|
||||
use App\Http\Requests\Client\ReactivateClientEmailRequest;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Task;
|
||||
use App\Transformers\DocumentTransformer;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -353,7 +353,7 @@ class ClientGatewayTokenController extends BaseController
|
||||
*/
|
||||
public function store(StoreClientGatewayTokenRequest $request)
|
||||
{
|
||||
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
|
@ -12,16 +12,17 @@
|
||||
|
||||
namespace App\Http\Controllers\ClientPortal;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ClientPortal\Documents\ShowDocumentRequest;
|
||||
use App\Http\Requests\Document\DownloadMultipleDocumentsRequest;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Document;
|
||||
use App\Utils\TempFile;
|
||||
use App\Models\Document;
|
||||
use Illuminate\View\View;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\View\View;
|
||||
use App\Http\Requests\Document\DownloadMultipleDocumentsRequest;
|
||||
use App\Http\Requests\ClientPortal\Documents\ShowDocumentRequest;
|
||||
|
||||
class DocumentController extends Controller
|
||||
{
|
||||
@ -68,6 +69,30 @@ class DocumentController extends Controller
|
||||
return Storage::disk($document->disk)->download($document->url, $document->name, $headers);
|
||||
}
|
||||
|
||||
public function hashDownload(string $hash)
|
||||
{
|
||||
|
||||
$hash = Cache::pull($hash);
|
||||
|
||||
if(!$hash) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
MultiDB::setDb($hash['db']);
|
||||
|
||||
/** @var \App\Models\Document $document **/
|
||||
$document = Document::where('hash', $hash['doc_hash'])->firstOrFail();
|
||||
|
||||
$headers = ['Cache-Control:' => 'no-cache'];
|
||||
|
||||
if (request()->input('inline') == 'true') {
|
||||
$headers = array_merge($headers, ['Content-Disposition' => 'inline']);
|
||||
}
|
||||
|
||||
return Storage::disk($document->disk)->download($document->url, $document->name, $headers);
|
||||
}
|
||||
|
||||
|
||||
public function downloadMultiple(DownloadMultipleDocumentsRequest $request)
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Collection<Document> $documents **/
|
||||
|
@ -0,0 +1,60 @@
|
||||
<?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\ClientPortal;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\ClientContact;
|
||||
use App\Jobs\Mail\NinjaMailer;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Mail\Admin\ClientUnsubscribedObject;
|
||||
|
||||
class EmailPreferencesController extends Controller
|
||||
{
|
||||
public function index(string $entity, string $invitation_key, Request $request): \Illuminate\View\View
|
||||
{
|
||||
$class = "\\App\\Models\\".ucfirst(Str::camel($entity)).'Invitation';
|
||||
$invitation = $class::where('key', $invitation_key)->firstOrFail();
|
||||
|
||||
$data['receive_emails'] = $invitation->contact->is_locked ? false : true;
|
||||
$data['company'] = $invitation->company;
|
||||
|
||||
return $this->render('generic.email_preferences', $data);
|
||||
}
|
||||
|
||||
public function update(string $entity, string $invitation_key, Request $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$class = "\\App\\Models\\" . ucfirst(Str::camel($entity)) . 'Invitation';
|
||||
$invitation = $class::withTrashed()->where('key', $invitation_key)->firstOrFail();
|
||||
|
||||
$invitation->contact->is_locked = $request->action === 'unsubscribe' ? true : false;
|
||||
$invitation->contact->push();
|
||||
|
||||
if ($invitation->contact->is_locked && !Cache::has("unsubscribe_notitfication_suppression:{$invitation_key}")) {
|
||||
$nmo = new NinjaMailerObject();
|
||||
$nmo->mailable = new NinjaMailer((new ClientUnsubscribedObject($invitation->contact, $invitation->contact->company, $invitation->contact->company->owner()->company_users()->first()->portalType() ?? true))->build());
|
||||
$nmo->company = $invitation->contact->company;
|
||||
$nmo->to_user = $invitation->contact->company->owner();
|
||||
$nmo->settings = $invitation->contact->company->settings;
|
||||
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
Cache::put("unsubscribe_notitfication_suppression:{$invitation_key}", true, 3600);
|
||||
}
|
||||
|
||||
return back()->with('message', ctrans('texts.updated_settings'));
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Models\RecurringInvoiceInvitation;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
@ -68,11 +69,14 @@ class InvoiceController extends Controller
|
||||
event(new InvoiceWasViewed($invitation, $invoice->company, Ninja::eventVars()));
|
||||
}
|
||||
|
||||
$variables = ($invitation && auth()->guard('contact')->user()->client->getSetting('show_accept_invoice_terms')) ? (new HtmlEngine($invitation))->generateLabelsAndValues() : false;
|
||||
|
||||
$data = [
|
||||
'invoice' => $invoice,
|
||||
'invitation' => $invitation ?: $invoice->invitations->first(),
|
||||
'key' => $invitation ? $invitation->key : false,
|
||||
'hash' => $hash,
|
||||
'variables' => $variables,
|
||||
];
|
||||
|
||||
if ($request->query('mode') === 'fullscreen') {
|
||||
@ -217,13 +221,22 @@ class InvoiceController extends Controller
|
||||
|
||||
//if there is only one payment method -> lets return straight to the payment page
|
||||
|
||||
$settings = auth()->guard('contact')->user()->client->getMergedSettings();
|
||||
$variables = false;
|
||||
|
||||
if(($invitation = $invoices->first()->invitations()->first() ?? false) && $settings->show_accept_invoice_terms) {
|
||||
$variables = (new HtmlEngine($invitation))->generateLabelsAndValues();
|
||||
}
|
||||
|
||||
$data = [
|
||||
'settings' => auth()->guard('contact')->user()->client->getMergedSettings(),
|
||||
'settings' => $settings,
|
||||
'invoices' => $invoices,
|
||||
'formatted_total' => $formatted_total,
|
||||
'payment_methods' => $payment_methods,
|
||||
'hashed_ids' => $invoices->pluck('hashed_id'),
|
||||
'total' => $total,
|
||||
'variables' => $variables,
|
||||
|
||||
];
|
||||
|
||||
return $this->render('invoices.payment', $data);
|
||||
|
@ -154,6 +154,7 @@ class NinjaPlanController extends Controller
|
||||
$account->is_trial = true;
|
||||
$account->hosted_company_count = 10;
|
||||
$account->trial_started = now();
|
||||
$account->trial_plan = 'pro';
|
||||
$account->save();
|
||||
}
|
||||
|
||||
|
@ -12,24 +12,25 @@
|
||||
|
||||
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\Payment;
|
||||
use App\Utils\HtmlEngine;
|
||||
use Illuminate\View\View;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentHash;
|
||||
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\Services\ClientPortal\InstantPayment;
|
||||
use App\Services\Subscription\SubscriptionService;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
|
||||
/**
|
||||
* Class PaymentController.
|
||||
@ -106,6 +107,12 @@ class PaymentController extends Controller
|
||||
*/
|
||||
public function process(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'contact_first_name' => ['required'],
|
||||
'contact_last_name' => ['required'],
|
||||
'contact_email' => ['required', 'email'],
|
||||
]);
|
||||
|
||||
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.
|
||||
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 = [
|
||||
'invoice' => $invoice,
|
||||
'key' => false,
|
||||
'invitation' => $invoice->invitations->first()
|
||||
'invitation' => $invitation,
|
||||
'variables' => $variables,
|
||||
];
|
||||
|
||||
if ($request->query('mode') === 'fullscreen') {
|
||||
|
@ -144,7 +144,10 @@ class PaymentMethodController extends Controller
|
||||
try {
|
||||
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->save();
|
||||
|
||||
} catch (Exception $e) {
|
||||
nlog($e->getMessage());
|
||||
|
||||
|
@ -12,21 +12,22 @@
|
||||
|
||||
namespace App\Http\Controllers\ClientPortal;
|
||||
|
||||
use App\Events\Misc\InvitationWasViewed;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Quote;
|
||||
use App\Utils\HtmlEngine;
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Events\Quote\QuoteWasViewed;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ClientPortal\Quotes\ProcessQuotesInBulkRequest;
|
||||
use App\Jobs\Invoice\InjectSignature;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use App\Events\Misc\InvitationWasViewed;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use App\Http\Requests\ClientPortal\Quotes\ShowQuoteRequest;
|
||||
use App\Http\Requests\ClientPortal\Quotes\ShowQuotesRequest;
|
||||
use App\Jobs\Invoice\InjectSignature;
|
||||
use App\Models\Quote;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use App\Http\Requests\ClientPortal\Quotes\ProcessQuotesInBulkRequest;
|
||||
|
||||
class QuoteController extends Controller
|
||||
{
|
||||
@ -54,11 +55,13 @@ class QuoteController extends Controller
|
||||
/* If the quote is expired, convert the status here */
|
||||
|
||||
$invitation = $quote->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first();
|
||||
$variables = ($invitation && auth()->guard('contact')->user()->client->getSetting('show_accept_quote_terms')) ? (new HtmlEngine($invitation))->generateLabelsAndValues() : false;
|
||||
|
||||
$data = [
|
||||
'quote' => $quote,
|
||||
'key' => $invitation ? $invitation->key : false,
|
||||
'invitation' => $invitation
|
||||
'invitation' => $invitation,
|
||||
'variables' => $variables,
|
||||
];
|
||||
|
||||
if ($invitation && auth()->guard('contact') && ! request()->has('silent') && ! $invitation->viewed_date) {
|
||||
@ -212,8 +215,18 @@ class QuoteController extends Controller
|
||||
->withSuccess('Quote(s) approved successfully.');
|
||||
}
|
||||
|
||||
|
||||
$variables = false;
|
||||
|
||||
if($invitation = $quotes->first()->invitations()->first() ?? false) {
|
||||
$variables = (new HtmlEngine($invitation))->generateLabelsAndValues();
|
||||
}
|
||||
|
||||
$variables = ($invitation && auth()->guard('contact')->user()->client->getSetting('show_accept_quote_terms')) ? (new HtmlEngine($invitation))->generateLabelsAndValues() : false;
|
||||
|
||||
return $this->render('quotes.approve', [
|
||||
'quotes' => $quotes,
|
||||
'variables' => $variables,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,6 @@ class SubscriptionPlanSwitchController extends Controller
|
||||
|
||||
public function not_availabe()
|
||||
{
|
||||
abort(404, 'ewwo');
|
||||
abort(404, 'Not Available');
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ class CompanyGatewayController extends BaseController
|
||||
*/
|
||||
public function create(CreateCompanyGatewayRequest $request)
|
||||
{
|
||||
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
|
@ -64,7 +64,7 @@ class CompanyLedgerController extends BaseController
|
||||
*/
|
||||
public function index(ShowCompanyLedgerRequest $request)
|
||||
{
|
||||
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
|
@ -121,7 +121,7 @@ class ConnectedAccountController extends BaseController
|
||||
'email_verified_at' => now()
|
||||
];
|
||||
|
||||
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
@ -169,13 +169,16 @@ class ConnectedAccountController extends BaseController
|
||||
'email_verified_at' => now(),
|
||||
];
|
||||
|
||||
auth()->user()->update($connected_account);
|
||||
auth()->user()->email_verified_at = now();
|
||||
auth()->user()->save();
|
||||
/** @var \App\Models\User $logged_in_user */
|
||||
$logged_in_user = auth()->user();
|
||||
|
||||
$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()
|
||||
@ -214,20 +217,22 @@ class ConnectedAccountController extends BaseController
|
||||
// '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);
|
||||
}
|
||||
|
||||
auth()->user()->update($connected_account);
|
||||
auth()->user()->email_verified_at = now();
|
||||
auth()->user()->oauth_user_token = $token;
|
||||
auth()->user()->oauth_user_refresh_token = $refresh_token;
|
||||
$logged_in_user->update($connected_account);
|
||||
$logged_in_user->email_verified_at = now();
|
||||
$logged_in_user->oauth_user_token = $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(auth()->user());
|
||||
return $this->itemResponse($logged_in_user);
|
||||
}
|
||||
|
||||
return response()
|
||||
|
@ -128,7 +128,7 @@ class ExpenseCategoryController extends BaseController
|
||||
*/
|
||||
public function create(CreateExpenseCategoryRequest $request)
|
||||
{
|
||||
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
|
@ -21,7 +21,7 @@ class HostedMigrationController extends Controller
|
||||
{
|
||||
public function checkStatus(Request $request)
|
||||
{
|
||||
|
||||
|
||||
if ($request->header('X-API-HOSTED-SECRET') != config('ninja.ninja_hosted_secret')) {
|
||||
return;
|
||||
}
|
||||
@ -29,19 +29,20 @@ class HostedMigrationController extends Controller
|
||||
MultiDB::findAndSetDbByCompanyKey($request->company_key);
|
||||
$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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// 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)))
|
||||
|
||||
// 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\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' => '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);
|
||||
|
@ -244,19 +244,23 @@ class ImportController extends Controller
|
||||
*/
|
||||
public function detectDelimiter($csvfile): string
|
||||
{
|
||||
$delimiters = [',', '.', ';'];
|
||||
$bestDelimiter = ' ';
|
||||
|
||||
$delimiters = [',', '.', ';', '|'];
|
||||
$bestDelimiter = ',';
|
||||
$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) {
|
||||
|
||||
if (substr_count(strstr($csvfile, "\n", true), $delimiter) >= $count) {
|
||||
$count = substr_count(strstr($csvfile, "\n", true), $delimiter);
|
||||
$count = substr_count($csvfile, $delimiter);
|
||||
$bestDelimiter = $delimiter;
|
||||
}
|
||||
|
||||
}
|
||||
return $bestDelimiter ?? ',';
|
||||
|
||||
return $bestDelimiter;
|
||||
}
|
||||
}
|
||||
|
@ -22,18 +22,22 @@ class MailgunWebhookController extends BaseController
|
||||
{
|
||||
private $invitation;
|
||||
|
||||
public function __construct() {}
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function webhook(Request $request)
|
||||
{
|
||||
|
||||
$input = $request->all();
|
||||
|
||||
if (\abs(\time() - $request['signature']['timestamp']) > 15)
|
||||
if (\abs(\time() - $request['signature']['timestamp']) > 15) {
|
||||
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);
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Success.'], 200);
|
||||
}
|
||||
|
@ -520,7 +520,7 @@ class PaymentController extends BaseController
|
||||
if($action == 'template' && $user->can('view', $payments->first())) {
|
||||
|
||||
$hash_or_response = request()->boolean('send_email') ? 'email sent' : \Illuminate\Support\Str::uuid();
|
||||
nlog($payments->pluck('hashed_id')->toArray());
|
||||
|
||||
TemplateAction::dispatch(
|
||||
$payments->pluck('hashed_id')->toArray(),
|
||||
$request->template_id,
|
||||
|
@ -316,8 +316,8 @@ class PreviewPurchaseOrderController extends BaseController
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
//if phantom js...... inject here..
|
||||
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
|
||||
|
@ -181,7 +181,7 @@ class ProductController extends BaseController
|
||||
*/
|
||||
public function store(StoreProductRequest $request)
|
||||
{
|
||||
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
|
@ -399,7 +399,6 @@ class QuoteController extends BaseController
|
||||
|
||||
$quote->service()
|
||||
->triggeredActions($request);
|
||||
// ->deletePdf();
|
||||
|
||||
event(new QuoteWasUpdated($quote, $quote->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
|
@ -50,7 +50,7 @@ class SearchController extends Controller
|
||||
->when(!$user->hasPermission('view_all') || !$user->hasPermission('view_client'), function ($query) use ($user) {
|
||||
$query->where('user_id', $user->id);
|
||||
})
|
||||
->orderBy('id', 'desc')
|
||||
->orderBy('updated_at', 'desc')
|
||||
->take(1000)
|
||||
->get();
|
||||
|
||||
@ -59,7 +59,7 @@ class SearchController extends Controller
|
||||
'name' => $client->present()->name(),
|
||||
'type' => '/client',
|
||||
'id' => $client->hashed_id,
|
||||
'path' => "/clients/{$client->hashed_id}/edit"
|
||||
'path' => "/clients/{$client->hashed_id}"
|
||||
];
|
||||
|
||||
$client->contacts->each(function ($contact) {
|
||||
|
64
app/Http/Controllers/SmtpController.php
Normal file
64
app/Http/Controllers/SmtpController.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?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);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -84,6 +84,9 @@ class StripeConnectController extends BaseController
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $request->input('code'),
|
||||
]);
|
||||
|
||||
nlog($response);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return view('auth.connect.access_denied');
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ class TwoFactorController extends BaseController
|
||||
|
||||
public function disableTwoFactor()
|
||||
{
|
||||
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
|
@ -499,7 +499,7 @@ class VendorController extends BaseController
|
||||
|
||||
$ids = request()->input('ids');
|
||||
$vendors = Vendor::withTrashed()->find($this->transformKeys($ids));
|
||||
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
|
@ -101,7 +101,9 @@ class PurchaseOrderController extends Controller
|
||||
'settings' => $purchase_order->company->settings,
|
||||
'sidebar' => $this->sidebarMenu(),
|
||||
'company' => $purchase_order->company,
|
||||
'invitation' => $invitation
|
||||
'invitation' => $invitation,
|
||||
'variables' => false,
|
||||
|
||||
];
|
||||
|
||||
if ($request->query('mode') === 'fullscreen') {
|
||||
|
@ -67,13 +67,14 @@ class TokenAuth
|
||||
$truth->setUser($company_token->user);
|
||||
$truth->setCompany($company_token->company);
|
||||
$truth->setCompanyToken($company_token);
|
||||
|
||||
$truth->setPremiumHosted($company_token->account->isPremium());
|
||||
/*
|
||||
| This method binds the db to the jobs created using this
|
||||
| session
|
||||
*/
|
||||
app('queue')->createPayloadUsing(function () use ($company_token) {
|
||||
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
|
||||
|
@ -22,10 +22,7 @@ class BulkBankTransactionRequest extends Request
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
/** @var \App\Models\User $user **/
|
||||
$user = auth()->user();
|
||||
|
||||
return $user->isAdmin();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
|
@ -23,6 +23,9 @@ class CreateBankTransactionRequest extends Request
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth()->user()->can('create', BankTransaction::class);
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
return $user->can('create', BankTransaction::class);
|
||||
}
|
||||
}
|
||||
|
30
app/Http/Requests/Client/ClientDocumentsRequest.php
Normal file
30
app/Http/Requests/Client/ClientDocumentsRequest.php
Normal 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);
|
||||
}
|
||||
}
|
@ -22,6 +22,9 @@ class PurgeClientRequest extends Request
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
return $user->isAdmin();
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,9 @@ class StoreClientRequest extends Request
|
||||
} elseif ($this->file('documents')) {
|
||||
$rules['documents'] = $this->file_validation;
|
||||
}
|
||||
else {
|
||||
$rules['documents'] = 'bail|sometimes|array';
|
||||
}
|
||||
|
||||
if ($this->file('file') && is_array($this->file('file'))) {
|
||||
$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['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;
|
||||
}
|
||||
|
@ -53,6 +53,8 @@ class UpdateClientRequest extends Request
|
||||
$rules['file.*'] = $this->file_validation;
|
||||
} elseif ($this->file('file')) {
|
||||
$rules['file'] = $this->file_validation;
|
||||
} else {
|
||||
$rules['documents'] = 'bail|sometimes|array';
|
||||
}
|
||||
|
||||
$rules['company_logo'] = 'mimes:jpeg,jpg,png,gif|max:10000';
|
||||
@ -60,7 +62,7 @@ class UpdateClientRequest extends Request
|
||||
$rules['size_id'] = 'integer|nullable';
|
||||
$rules['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) {
|
||||
$rules['id_number'] = Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id);
|
||||
|
@ -11,13 +11,14 @@
|
||||
|
||||
namespace App\Http\Requests\Company;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\ValidationRules\Company\ValidCompanyQuantity;
|
||||
use App\Http\ValidationRules\Company\ValidSubdomain;
|
||||
use App\Http\ValidationRules\ValidSettingsRule;
|
||||
use App\Models\Company;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Company;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Http\ValidationRules\ValidSettingsRule;
|
||||
use App\Http\ValidationRules\Company\ValidSubdomain;
|
||||
use App\Http\ValidationRules\Company\ValidCompanyQuantity;
|
||||
|
||||
class StoreCompanyRequest extends Request
|
||||
{
|
||||
@ -55,6 +56,15 @@ class StoreCompanyRequest extends Request
|
||||
}
|
||||
}
|
||||
|
||||
$rules['smtp_host'] = 'sometimes|string|nullable';
|
||||
$rules['smtp_port'] = 'sometimes|string|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;
|
||||
}
|
||||
|
||||
@ -66,14 +76,29 @@ class StoreCompanyRequest extends Request
|
||||
$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'];
|
||||
}
|
||||
|
||||
if (array_key_exists('portal_domain', $input)) {
|
||||
if (isset($input['portal_domain'])) {
|
||||
$input['portal_domain'] = rtrim(strtolower($input['portal_domain']), "/");
|
||||
}
|
||||
|
||||
if(Ninja::isHosted() && !isset($input['subdomain'])) {
|
||||
$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_verify_peer']) && is_string($input['smtp_verify_peer']))
|
||||
$input['smtp_verify_peer'] == 'true' ? true : false;
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
@ -57,8 +57,14 @@ class UpdateCompanyRequest extends Request
|
||||
$rules['matomo_id'] = 'nullable|integer';
|
||||
$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['client_registration_fields'] = 'array';
|
||||
|
||||
$rules['smtp_host'] = 'sometimes|string|nullable';
|
||||
$rules['smtp_port'] = 'sometimes|string|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')) {
|
||||
$rules['portal_domain'] = 'bail|nullable|sometimes|url';
|
||||
}
|
||||
@ -74,23 +80,35 @@ class UpdateCompanyRequest extends Request
|
||||
{
|
||||
$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'] = rtrim(strtolower($input['portal_domain']), "/");
|
||||
}
|
||||
|
||||
if (array_key_exists('settings', $input)) {
|
||||
if (isset($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']);
|
||||
}
|
||||
|
||||
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']);
|
||||
}
|
||||
|
||||
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_verify_peer']) && is_string($input['smtp_verify_peer'])) {
|
||||
$input['smtp_verify_peer'] == 'true' ? true : false;
|
||||
}
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
|
@ -50,6 +50,8 @@ class StoreCreditRequest extends Request
|
||||
$rules['documents.*'] = $this->file_validation;
|
||||
} elseif ($this->file('documents')) {
|
||||
$rules['documents'] = $this->file_validation;
|
||||
}else {
|
||||
$rules['documents'] = 'bail|sometimes|array';
|
||||
}
|
||||
|
||||
if ($this->file('file') && is_array($this->file('file'))) {
|
||||
|
@ -52,6 +52,8 @@ class UpdateCreditRequest extends Request
|
||||
$rules['documents.*'] = $this->file_validation;
|
||||
} elseif ($this->file('documents')) {
|
||||
$rules['documents'] = $this->file_validation;
|
||||
}else {
|
||||
$rules['documents'] = 'bail|sometimes|array';
|
||||
}
|
||||
|
||||
if ($this->file('file') && is_array($this->file('file'))) {
|
||||
|
@ -26,7 +26,7 @@ class StoreDocumentRequest extends Request
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
return $user->can('create', Document::class);
|
||||
return $user->can('create', Document::class) || ($user->hasIntersectPermissions(['edit_all', 'create_all']));
|
||||
}
|
||||
|
||||
public function rules()
|
||||
|
@ -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['payment_date'] = 'bail|nullable|sometimes|date:Y-m-d';
|
||||
$rules['date'] = 'bail|sometimes|date:Y-m-d';
|
||||
$rules['documents'] = 'bail|sometimes|array';
|
||||
|
||||
return $this->globalRules($rules);
|
||||
}
|
||||
|
@ -29,25 +29,32 @@ class UpdateExpenseRequest extends Request
|
||||
*/
|
||||
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()
|
||||
{
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
/* Ensure we have a client name, and that all emails are unique*/
|
||||
$rules = [];
|
||||
|
||||
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) {
|
||||
$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['transaction_id'] = 'bail|sometimes|nullable|exists:bank_transactions,id,company_id,'.auth()->user()->company()->id;
|
||||
$rules['invoice_id'] = 'bail|sometimes|nullable|exists:invoices,id,company_id,'.auth()->user()->company()->id;
|
||||
$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,'.$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);
|
||||
@ -55,6 +62,10 @@ class UpdateExpenseRequest extends Request
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$input = $this->all();
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
@ -64,7 +75,7 @@ class UpdateExpenseRequest extends Request
|
||||
}
|
||||
|
||||
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 */
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Http\Requests\ExpenseCategory;
|
||||
|
||||
use App\Models\Expense;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\ExpenseCategory;
|
||||
|
||||
@ -23,14 +24,21 @@ class StoreExpenseCategoryRequest extends Request
|
||||
*/
|
||||
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()
|
||||
{
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$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);
|
||||
}
|
||||
|
@ -26,16 +26,24 @@ class UpdateExpenseCategoryRequest extends Request
|
||||
*/
|
||||
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()
|
||||
{
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$rules = [];
|
||||
|
||||
if ($this->input('name')) {
|
||||
// $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;
|
||||
|
@ -47,6 +47,8 @@ class StoreInvoiceRequest extends Request
|
||||
$rules['documents.*'] = $this->file_validation;
|
||||
} elseif ($this->file('documents')) {
|
||||
$rules['documents'] = $this->file_validation;
|
||||
}else {
|
||||
$rules['documents'] = 'bail|sometimes|array';
|
||||
}
|
||||
|
||||
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_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date'];
|
||||
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user