Merge remote-tracking branch 'upstream/v5-develop' into 1314-subscriptions-v3

This commit is contained in:
Benjamin Beganović 2024-02-21 18:02:10 +01:00
commit 4cee352009
203 changed files with 314925 additions and 312590 deletions

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

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

View File

@ -1 +1 @@
5.8.24 5.8.27

View File

@ -52,8 +52,9 @@ class EncryptNinja extends Command
*/ */
public function handle() public function handle()
{ {
if($this->option('encrypt')) if($this->option('encrypt')) {
return $this->encryptFiles(); return $this->encryptFiles();
}
if($this->option('decrypt')) { if($this->option('decrypt')) {
return $this->decryptFiles(); return $this->decryptFiles();
@ -67,7 +68,7 @@ class EncryptNinja extends Command
$contents = Storage::disk('base')->get($file); $contents = Storage::disk('base')->get($file);
$encrypted = encrypt($contents); $encrypted = encrypt($contents);
Storage::disk('base')->put($file.".enc", $encrypted); Storage::disk('base')->put($file.".enc", $encrypted);
Storage::disk('base')->delete($file); // Storage::disk('base')->delete($file);
} }
} }

View File

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

View File

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

View File

@ -451,13 +451,15 @@ class BaseExport
{ {
if (isset($this->input['client_id']) && $this->input['client_id'] != 'all') { if (isset($this->input['client_id']) && $this->input['client_id'] != 'all') {
if(!is_int($this->input['client_id'])) if(!is_int($this->input['client_id'])) {
$this->input['client_id'] = $this->decodePrimaryKey($this->input['client_id']); $this->input['client_id'] = $this->decodePrimaryKey($this->input['client_id']);
}
$client = Client::withTrashed()->find($this->input['client_id']); $client = Client::withTrashed()->find($this->input['client_id']);
if(!$client) if(!$client) {
return $query; return $query;
}
$this->client_description = $client->present()->name; $this->client_description = $client->present()->name;
return $query->where('client_id', $this->input['client_id']); return $query->where('client_id', $this->input['client_id']);
@ -845,16 +847,18 @@ class BaseExport
protected function addClientFilter($query, $clients): Builder protected function addClientFilter($query, $clients): Builder
{ {
if(is_string($clients)) if(is_string($clients)) {
$clients = explode(',', $clients); $clients = explode(',', $clients);
}
$transformed_clients = $this->transformKeys($clients); $transformed_clients = $this->transformKeys($clients);
nlog($clients); nlog($clients);
nlog($transformed_clients); nlog($transformed_clients);
if(count($transformed_clients) > 0) if(count($transformed_clients) > 0) {
$query->whereIn('client_id', $transformed_clients); $query->whereIn('client_id', $transformed_clients);
}
return $query; return $query;
} }
@ -868,8 +872,9 @@ class BaseExport
$transformed_vendors = $this->transformKeys($vendors); $transformed_vendors = $this->transformKeys($vendors);
if(count($transformed_vendors) > 0) if(count($transformed_vendors) > 0) {
$query->whereIn('vendor_id', $transformed_vendors); $query->whereIn('vendor_id', $transformed_vendors);
}
return $query; return $query;
} }
@ -883,8 +888,9 @@ class BaseExport
$transformed_projects = $this->transformKeys($projects); $transformed_projects = $this->transformKeys($projects);
if(count($transformed_projects) > 0) if(count($transformed_projects) > 0) {
$query->whereIn('project_id', $transformed_projects); $query->whereIn('project_id', $transformed_projects);
}
return $query; return $query;
} }
@ -899,8 +905,9 @@ class BaseExport
$transformed_expense_categories = $this->transformKeys($expense_categories); $transformed_expense_categories = $this->transformKeys($expense_categories);
if(count($transformed_expense_categories) > 0) if(count($transformed_expense_categories) > 0) {
$query->whereIn('category_id', $transformed_expense_categories); $query->whereIn('category_id', $transformed_expense_categories);
}
return $query; return $query;
} }
@ -1299,9 +1306,9 @@ class BaseExport
public function queueDocuments(Builder $query) public function queueDocuments(Builder $query)
{ {
nlog("queue docs pls"); nlog("queue docs pls");
if($query->getModel() instanceof Document) if($query->getModel() instanceof Document) {
$documents = $query->pluck('id')->toArray(); $documents = $query->pluck('id')->toArray();
else{ } else {
$documents = $query->cursor() $documents = $query->cursor()
->map(function ($entity) { ->map(function ($entity) {
return $entity->documents()->pluck('id')->toArray(); return $entity->documents()->pluck('id')->toArray();
@ -1315,11 +1322,13 @@ class BaseExport
$user = $this->company->owner(); $user = $this->company->owner();
if(auth()->user() && auth()->user()->account_id == $this->company->account_id) if(auth()->user() && auth()->user()->account_id == $this->company->account_id) {
$user = auth()->user(); $user = auth()->user();
}
if($this->input['user_id'] ?? false) if($this->input['user_id'] ?? false) {
$user = User::where('id', $this->input['user_id'])->where('account_id', $this->company->account_id)->first(); $user = User::where('id', $this->input['user_id'])->where('account_id', $this->company->account_id)->first();
}
ZipDocuments::dispatch($documents, $this->company, $user); ZipDocuments::dispatch($documents, $this->company, $user);
} }

View File

@ -23,7 +23,6 @@ use League\Csv\Writer;
class ExpenseExport extends BaseExport class ExpenseExport extends BaseExport
{ {
private $expense_transformer; private $expense_transformer;
private Decorator $decorator; private Decorator $decorator;
@ -206,14 +205,12 @@ class ExpenseExport extends BaseExport
if($expense->calculate_tax_by_amount) { if($expense->calculate_tax_by_amount) {
$total_tax_amount = round($expense->tax_amount1 + $expense->tax_amount2 + $expense->tax_amount3, $precision); $total_tax_amount = round($expense->tax_amount1 + $expense->tax_amount2 + $expense->tax_amount3, $precision);
} } else {
else {
if($expense->uses_inclusive_taxes) { if($expense->uses_inclusive_taxes) {
$total_tax_amount = ($this->calcInclusiveLineTax($expense->tax_rate1 ?? 0, $expense->amount, $precision)) + ($this->calcInclusiveLineTax($expense->tax_rate2 ?? 0, $expense->amount, $precision)) + ($this->calcInclusiveLineTax($expense->tax_rate3 ?? 0, $expense->amount, $precision)); $total_tax_amount = ($this->calcInclusiveLineTax($expense->tax_rate1 ?? 0, $expense->amount, $precision)) + ($this->calcInclusiveLineTax($expense->tax_rate2 ?? 0, $expense->amount, $precision)) + ($this->calcInclusiveLineTax($expense->tax_rate3 ?? 0, $expense->amount, $precision));
$entity['expense.net_amount'] = round(($expense->amount - round($total_tax_amount, $precision)), $precision); $entity['expense.net_amount'] = round(($expense->amount - round($total_tax_amount, $precision)), $precision);
} } else {
else{
$total_tax_amount = ($expense->amount * (($expense->tax_rate1 ?? 0) / 100)) + ($expense->amount * (($expense->tax_rate2 ?? 0) / 100)) + ($expense->amount * (($expense->tax_rate3 ?? 0) / 100)); $total_tax_amount = ($expense->amount * (($expense->tax_rate1 ?? 0) / 100)) + ($expense->amount * (($expense->tax_rate2 ?? 0) / 100)) + ($expense->amount * (($expense->tax_rate3 ?? 0) / 100));
$entity['expense.net_amount'] = round(($expense->amount + round($total_tax_amount, $precision)), $precision); $entity['expense.net_amount'] = round(($expense->amount + round($total_tax_amount, $precision)), $precision);
} }

View File

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

View File

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

View File

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

View File

@ -12,8 +12,9 @@
namespace App\Filters; namespace App\Filters;
use App\Models\Payment; use App\Models\Payment;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Eloquent\Builder;
/** /**
* PaymentFilters. * PaymentFilters.
@ -163,7 +164,7 @@ class PaymentFilters extends QueryFilters
{ {
$sort_col = explode('|', $sort); $sort_col = explode('|', $sort);
if (!is_array($sort_col) || count($sort_col) != 2) { if (!is_array($sort_col) || count($sort_col) != 2 || !in_array($sort_col, Schema::getColumnListing('payments'))) {
return $this->builder; return $this->builder;
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -74,8 +74,9 @@ class DocumentController extends Controller
$hash = Cache::pull($hash); $hash = Cache::pull($hash);
if(!$hash) if(!$hash) {
abort(404); abort(404);
}
MultiDB::setDb($hash['db']); MultiDB::setDb($hash['db']);

View File

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

View File

@ -224,8 +224,9 @@ class InvoiceController extends Controller
$settings = auth()->guard('contact')->user()->client->getMergedSettings(); $settings = auth()->guard('contact')->user()->client->getMergedSettings();
$variables = false; $variables = false;
if(($invitation = $invoices->first()->invitations()->first() ?? false) && $settings->show_accept_invoice_terms) if(($invitation = $invoices->first()->invitations()->first() ?? false) && $settings->show_accept_invoice_terms) {
$variables = (new HtmlEngine($invitation))->generateLabelsAndValues(); $variables = (new HtmlEngine($invitation))->generateLabelsAndValues();
}
$data = [ $data = [
'settings' => $settings, 'settings' => $settings,

View File

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

View File

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

View File

@ -29,8 +29,9 @@ class HostedMigrationController extends Controller
MultiDB::findAndSetDbByCompanyKey($request->company_key); MultiDB::findAndSetDbByCompanyKey($request->company_key);
$c = Company::where('company_key', $request->company_key)->first(); $c = Company::where('company_key', $request->company_key)->first();
if(!$c || $c->is_disabled) if(!$c || $c->is_disabled) {
return response()->json(['message' => 'ok'], 200); return response()->json(['message' => 'ok'], 200);
}
// if(\App\Models\Invoice::query()->where('company_id', $c->id)->where('created_at', '>', now()->subMonths(2))->first()) // if(\App\Models\Invoice::query()->where('company_id', $c->id)->where('created_at', '>', now()->subMonths(2))->first())
// return response()->json(['message' => 'New data exists, are you sure? Please log in here https://app.invoicing.co and delete the company if you really need to migrate again.'], 400); // return response()->json(['message' => 'New data exists, are you sure? Please log in here https://app.invoicing.co and delete the company if you really need to migrate again.'], 400);

View File

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

View File

@ -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);
}
}

View File

@ -67,7 +67,7 @@ class TokenAuth
$truth->setUser($company_token->user); $truth->setUser($company_token->user);
$truth->setCompany($company_token->company); $truth->setCompany($company_token->company);
$truth->setCompanyToken($company_token); $truth->setCompanyToken($company_token);
$truth->setPremiumHosted($company_token->account->isPremium());
/* /*
| This method binds the db to the jobs created using this | This method binds the db to the jobs created using this
| session | session

View File

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

View File

@ -53,6 +53,8 @@ class UpdateClientRequest extends Request
$rules['file.*'] = $this->file_validation; $rules['file.*'] = $this->file_validation;
} elseif ($this->file('file')) { } elseif ($this->file('file')) {
$rules['file'] = $this->file_validation; $rules['file'] = $this->file_validation;
} else {
$rules['documents'] = 'bail|sometimes|array';
} }
$rules['company_logo'] = 'mimes:jpeg,jpg,png,gif|max:10000'; $rules['company_logo'] = 'mimes:jpeg,jpg,png,gif|max:10000';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -49,6 +49,8 @@ class StoreQuoteRequest extends Request
$rules['documents.*'] = $this->file_validation; $rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) { } elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation; $rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
} }
if ($this->file('file') && is_array($this->file('file'))) { if ($this->file('file') && is_array($this->file('file'))) {
@ -59,11 +61,8 @@ class StoreQuoteRequest extends Request
$rules['number'] = ['nullable', Rule::unique('quotes')->where('company_id', $user->company()->id)]; $rules['number'] = ['nullable', Rule::unique('quotes')->where('company_id', $user->company()->id)];
$rules['discount'] = 'sometimes|numeric'; $rules['discount'] = 'sometimes|numeric';
$rules['is_amount_discount'] = ['boolean']; $rules['is_amount_discount'] = ['boolean'];
$rules['exchange_rate'] = 'bail|sometimes|numeric'; $rules['exchange_rate'] = 'bail|sometimes|numeric';
// $rules['number'] = new UniqueQuoteNumberRule($this->all());
$rules['line_items'] = 'array'; $rules['line_items'] = 'array';
return $rules; return $rules;

View File

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

View File

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

View File

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

View File

@ -198,17 +198,21 @@ class Request extends FormRequest
} }
} }
if(isset($input['public_notes'])) if(isset($input['public_notes'])) {
$input['public_notes'] = str_replace("</sc", "<-", $input['public_notes']); $input['public_notes'] = str_replace("</sc", "<-", $input['public_notes']);
}
if(isset($input['footer'])) if(isset($input['footer'])) {
$input['footer'] = str_replace("</sc", "<-", $input['footer']); $input['footer'] = str_replace("</sc", "<-", $input['footer']);
}
if(isset($input['terms'])) if(isset($input['terms'])) {
$input['terms'] = str_replace("</sc", "<-", $input['terms']); $input['terms'] = str_replace("</sc", "<-", $input['terms']);
}
if(isset($input['private_notes'])) if(isset($input['private_notes'])) {
$input['private_notes'] = str_replace("</sc", "<-", $input['private_notes']); $input['private_notes'] = str_replace("</sc", "<-", $input['private_notes']);
}
return $input; return $input;
} }

View File

@ -0,0 +1,54 @@
<?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\Smtp;
use App\Http\Requests\Request;
class CheckSmtpRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->isAdmin();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
];
}
public function prepareForValidation()
{
$input = $this->input();
if(isset($input['smtp_username']) && $input['smtp_username'] == '********')
unset($input['smtp_username']);
if(isset($input['smtp_password'])&& $input['smtp_password'] == '********')
unset($input['smtp_password']);
$this->replace($input);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -90,8 +90,7 @@ class PaymentAppliedValidAmount implements Rule
if($inv->status_id == Invoice::STATUS_DRAFT && $inv->amount >= $invoice['amount']) { if($inv->status_id == Invoice::STATUS_DRAFT && $inv->amount >= $invoice['amount']) {
} } elseif ($inv->balance < $invoice['amount']) {
elseif ($inv->balance < $invoice['amount']) {
$this->message = 'Amount cannot be greater than invoice balance'; $this->message = 'Amount cannot be greater than invoice balance';
return false; return false;

View File

@ -117,10 +117,8 @@ class Wave extends BaseImport implements ImportInterface
$this->transformer = new InvoiceTransformer($this->company); $this->transformer = new InvoiceTransformer($this->company);
foreach($data as $key => $invoice) foreach($data as $key => $invoice) {
{ if(!isset($invoice['Invoice Number']) || empty($invoice['Invoice Number'])) {
if(!isset($invoice['Invoice Number']) || empty($invoice['Invoice Number']))
{
unset($data[$key]); unset($data[$key]);
} }
} }

View File

@ -315,14 +315,11 @@ class BaseTransformer
public function getFloat($data, $field) public function getFloat($data, $field)
{ {
if (array_key_exists($field, $data)) { if (array_key_exists($field, $data)) {
//$number = preg_replace('/[^0-9-.]+/', '', $data[$field]);
return Number::parseFloat($data[$field]); return Number::parseFloat($data[$field]);
} else {
//$number = 0;
return 0;
} }
// return Number::parseFloat($number); return 0;
} }
/** /**

View File

@ -138,7 +138,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue
private function processTransactions() private function processTransactions()
{ {
//Get transaction count object //Get transaction count object
$transactions = $this->nordigen->getTransactions($this->bank_integration->nordigen_account_id, $this->from_date); $transactions = $this->nordigen->getTransactions($this->company, $this->bank_integration->nordigen_account_id, $this->from_date);
//if no transactions, update the from_date and move on //if no transactions, update the from_date and move on
if (count($transactions) == 0) { if (count($transactions) == 0) {

View File

@ -1,65 +0,0 @@
<?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\Jobs\Cron;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\RecurringInvoice;
use Illuminate\Foundation\Bus\Dispatchable;
/*@not used*/
class CompanyRecurringCron
{
use Dispatchable;
public $tries = 1;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
}
/**
* Execute the job.
*
* @return void
*/
public function handle(): void
{
//multiDB environment, need to
foreach (MultiDB::$dbs as $db) {
MultiDB::setDB($db);
Company::where('is_disabled', 0)
->whereHas('recurring_invoices', function ($query) {
$query->where('next_send_date', '<=', now()->toDateTimeString())
->whereNotNull('next_send_date')
->whereNull('deleted_at')
->where('is_deleted', false)
->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->where('remaining_cycles', '!=', '0')
->whereHas('client', function ($query) {
$query->where('is_deleted', 0)
->where('deleted_at', null);
});
})
->cursor()->each(function ($company) {
SendCompanyRecurring::dispatch($company->id, $company->db);
});
}
}
}

View File

@ -51,7 +51,7 @@ class RecurringExpensesCron
Auth::logout(); Auth::logout();
if (! config('ninja.db.multi_db_enabled')) { if (! config('ninja.db.multi_db_enabled')) {
$recurring_expenses = RecurringExpense::where('next_send_date', '<=', now()->toDateTimeString()) $recurring_expenses = RecurringExpense::query()->where('next_send_date', '<=', now()->toDateTimeString())
->whereNotNull('next_send_date') ->whereNotNull('next_send_date')
->whereNull('deleted_at') ->whereNull('deleted_at')
->where('status_id', RecurringInvoice::STATUS_ACTIVE) ->where('status_id', RecurringInvoice::STATUS_ACTIVE)
@ -76,7 +76,7 @@ class RecurringExpensesCron
foreach (MultiDB::$dbs as $db) { foreach (MultiDB::$dbs as $db) {
MultiDB::setDB($db); MultiDB::setDB($db);
$recurring_expenses = RecurringExpense::where('next_send_date', '<=', now()->toDateTimeString()) $recurring_expenses = RecurringExpense::query()->where('next_send_date', '<=', now()->toDateTimeString())
->whereNotNull('next_send_date') ->whereNotNull('next_send_date')
->whereNull('deleted_at') ->whereNull('deleted_at')
->where('status_id', RecurringInvoice::STATUS_ACTIVE) ->where('status_id', RecurringInvoice::STATUS_ACTIVE)

View File

@ -48,7 +48,7 @@ class RecurringInvoicesCron
Auth::logout(); Auth::logout();
if (! config('ninja.db.multi_db_enabled')) { if (! config('ninja.db.multi_db_enabled')) {
$recurring_invoices = RecurringInvoice::where('status_id', RecurringInvoice::STATUS_ACTIVE) $recurring_invoices = RecurringInvoice::query()->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->where('is_deleted', false) ->where('is_deleted', false)
->where('remaining_cycles', '!=', '0') ->where('remaining_cycles', '!=', '0')
->whereNotNull('next_send_date') ->whereNotNull('next_send_date')
@ -87,7 +87,7 @@ class RecurringInvoicesCron
foreach (MultiDB::$dbs as $db) { foreach (MultiDB::$dbs as $db) {
MultiDB::setDB($db); MultiDB::setDB($db);
$recurring_invoices = RecurringInvoice::where('status_id', RecurringInvoice::STATUS_ACTIVE) $recurring_invoices = RecurringInvoice::query()->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->where('is_deleted', false) ->where('is_deleted', false)
->where('remaining_cycles', '!=', '0') ->where('remaining_cycles', '!=', '0')
->whereNull('deleted_at') ->whereNull('deleted_at')

View File

@ -1,74 +0,0 @@
<?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\Jobs\Cron;
use App\Jobs\RecurringInvoice\SendRecurring;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\RecurringInvoice;
use Illuminate\Foundation\Bus\Dispatchable;
/*@not used*/
class SendCompanyRecurring
{
use Dispatchable;
public $tries = 1;
/** @var \App\Models\Company $company */
public $company;
public function __construct(private int $company_id, private string $db)
{
}
/**
* Execute the job.
*
* @return void
*/
public function handle(): void
{
MultiDB::setDB($this->db);
$recurring_invoices = Company::where('id', $this->company_id)
->where('is_disabled', 0)
->whereHas('recurring_invoices', function ($query) {
$query->where('next_send_date', '<=', now()->toDateTimeString())
->whereNotNull('next_send_date')
->whereNull('deleted_at')
->where('is_deleted', false)
->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->where('remaining_cycles', '!=', '0')
->whereHas('client', function ($query) {
$query->where('is_deleted', 0)
->where('deleted_at', null);
});
})
->cursor()->each(function ($recurring_invoice) {
nlog("Trying to send {$recurring_invoice->number}");
if ($recurring_invoice->company->stop_on_unpaid_recurring) {
if ($recurring_invoice->invoices()->whereIn('status_id', [2, 3])->where('is_deleted', 0)->where('balance', '>', 0)->exists()) {
return;
}
}
try {
(new SendRecurring($recurring_invoice, $recurring_invoice->company->db))->handle();
} catch (\Exception $e) {
nlog("Unable to sending recurring invoice {$recurring_invoice->id} ".$e->getMessage());
}
});
}
}

View File

@ -52,7 +52,7 @@ class SubscriptionCron
->whereNotNull('subscription_id') ->whereNotNull('subscription_id')
->cursor(); ->cursor();
$invoices->each(function ($invoice) { $invoices->each(function (Invoice $invoice) {
$subscription = $invoice->subscription; $subscription = $invoice->subscription;
$body = [ $body = [
@ -80,7 +80,7 @@ class SubscriptionCron
->whereNotNull('subscription_id') ->whereNotNull('subscription_id')
->cursor(); ->cursor();
$invoices->each(function ($invoice) { $invoices->each(function (Invoice $invoice) {
$subscription = $invoice->subscription; $subscription = $invoice->subscription;
$body = [ $body = [

View File

@ -101,8 +101,7 @@ class NinjaMailerJob implements ShouldQueue
$this->nmo->mailable->replyTo($this->nmo->settings->reply_to_email, $reply_to_name); $this->nmo->mailable->replyTo($this->nmo->settings->reply_to_email, $reply_to_name);
} elseif(isset($this->nmo->invitation->user)) { } elseif(isset($this->nmo->invitation->user)) {
$this->nmo->mailable->replyTo($this->nmo->invitation->user->email, $this->nmo->invitation->user->present()->name()); $this->nmo->mailable->replyTo($this->nmo->invitation->user->email, $this->nmo->invitation->user->present()->name());
} } else {
else {
$this->nmo->mailable->replyTo($this->company->owner()->email, $this->company->owner()->present()->name()); $this->nmo->mailable->replyTo($this->company->owner()->email, $this->company->owner()->present()->name());
} }
@ -286,8 +285,7 @@ class NinjaMailerJob implements ShouldQueue
return $this; return $this;
} }
} } catch(\Exception $e) {
catch(\Exception $e){
nlog($e->getMessage()); nlog($e->getMessage());
} }
} }

View File

@ -16,7 +16,6 @@ namespace App\Jobs\Mail;
*/ */
class NinjaMailerObject class NinjaMailerObject
{ {
/* @var Illuminate\Mail\Mailable */ /* @var Illuminate\Mail\Mailable */
public $mailable; public $mailable;

View File

@ -34,7 +34,10 @@ use App\Notifications\Ninja\EmailBounceNotification;
class ProcessMailgunWebhook implements ShouldQueue class ProcessMailgunWebhook implements ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
public $tries = 1; public $tries = 1;
@ -89,8 +92,9 @@ class ProcessMailgunWebhook implements ShouldQueue
{ {
nlog($this->request); nlog($this->request);
if(!$this->request['event-data']['tags'][0]) if(!$this->request['event-data']['tags'][0]) {
return; return;
}
MultiDB::findAndSetDbByCompanyKey($this->request['event-data']['tags'][0]); MultiDB::findAndSetDbByCompanyKey($this->request['event-data']['tags'][0]);
$company = Company::where('company_key', $this->request['event-data']['tags'][0])->first(); $company = Company::where('company_key', $this->request['event-data']['tags'][0])->first();
@ -180,8 +184,9 @@ class ProcessMailgunWebhook implements ShouldQueue
$sl = $this->getSystemLog($this->request['MessageID']); $sl = $this->getSystemLog($this->request['MessageID']);
/** Prevents Gmail tracking from firing inappropriately */ /** Prevents Gmail tracking from firing inappropriately */
if($this->request['signature']['timestamp'] < $sl->log['signature']['timestamp'] + 3) if($this->request['signature']['timestamp'] < $sl->log['signature']['timestamp'] + 3) {
return; return;
}
$event = [ $event = [
'bounce_id' => '', 'bounce_id' => '',

View File

@ -94,6 +94,7 @@ class UserEmailChanged implements ShouldQueue
'logo' => $this->company->present()->logo(), 'logo' => $this->company->present()->logo(),
'settings' => $this->settings, 'settings' => $this->settings,
'whitelabel' => $this->company->account->isPaid() ? true : false, 'whitelabel' => $this->company->account->isPaid() ? true : false,
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
} }
} }

View File

@ -233,7 +233,6 @@ class Import implements ShouldQueue
['name' => ctrans('texts.ready_to_do'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 2], ['name' => ctrans('texts.ready_to_do'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 2],
['name' => ctrans('texts.in_progress'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 3], ['name' => ctrans('texts.in_progress'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 3],
['name' => ctrans('texts.done'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 4], ['name' => ctrans('texts.done'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 4],
]; ];
TaskStatus::insert($task_statuses); TaskStatus::insert($task_statuses);
@ -421,7 +420,7 @@ class Import implements ShouldQueue
if (Ninja::isHosted()) { if (Ninja::isHosted()) {
$data['subdomain'] = str_replace("_", "", $data['subdomain']); $data['subdomain'] = str_replace("_", "", ($data['subdomain'] ?? ''));
if (!MultiDB::checkDomainAvailable($data['subdomain'])) { if (!MultiDB::checkDomainAvailable($data['subdomain'])) {
$data['subdomain'] = MultiDB::randomSubdomainGenerator(); $data['subdomain'] = MultiDB::randomSubdomainGenerator();

View File

@ -94,7 +94,7 @@ class VersionCheck implements ShouldQueue
Client::doesntHave('contacts') Client::doesntHave('contacts')
->cursor() ->cursor()
->each(function ($client) { ->each(function (Client $client) {
$new_contact = ClientContactFactory::create($client->company_id, $client->user_id); $new_contact = ClientContactFactory::create($client->company_id, $client->user_id);
$new_contact->client_id = $client->id; $new_contact->client_id = $client->id;
@ -107,7 +107,7 @@ class VersionCheck implements ShouldQueue
Vendor::doesntHave('contacts') Vendor::doesntHave('contacts')
->cursor() ->cursor()
->each(function ($vendor) { ->each(function (Vendor $vendor) {
$new_contact = VendorContactFactory::create($vendor->company_id, $vendor->user_id); $new_contact = VendorContactFactory::create($vendor->company_id, $vendor->user_id);
$new_contact->vendor_id = $vendor->id; $new_contact->vendor_id = $vendor->id;

View File

@ -37,8 +37,9 @@ class InvoiceFailedEmailNotification
{ {
MultiDB::setDb($event->company->db); MultiDB::setDb($event->company->db);
if(Cache::has("invoice_failed_email_notification_{$event->invitation->key}")) if(Cache::has("invoice_failed_email_notification_{$event->invitation->key}")) {
return; return;
}
$invoice = $event->invitation->invoice; $invoice = $event->invitation->invoice;

View File

@ -44,6 +44,7 @@ class ClientUnsubscribedObject
'settings' => $this->company->settings, 'settings' => $this->company->settings,
'logo' => $this->company->present()->logo(), 'logo' => $this->company->present()->logo(),
'text_body' => "\n\n".ctrans('texts.client_unsubscribed_help', ['client' => $this->contact->present()->name()])."\n\n", 'text_body' => "\n\n".ctrans('texts.client_unsubscribed_help', ['client' => $this->contact->present()->name()])."\n\n",
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
$mail_obj = new \stdClass(); $mail_obj = new \stdClass();

View File

@ -94,6 +94,7 @@ class EntityCreatedObject
'settings' => $this->company->settings, 'settings' => $this->company->settings,
'whitelabel' => $this->company->account->isPaid() ? true : false, 'whitelabel' => $this->company->account->isPaid() ? true : false,
'text_body' => str_replace(['$view_button','$viewButton','$viewLink','$view_url'], '$view_url', $content), 'text_body' => str_replace(['$view_button','$viewButton','$viewLink','$view_url'], '$view_url', $content),
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
} else { } else {
$this->entity->load('client.country', 'client.company'); $this->entity->load('client.country', 'client.company');
@ -181,6 +182,7 @@ class EntityCreatedObject
'settings' => $settings, 'settings' => $settings,
'whitelabel' => $this->company->account->isPaid() ? true : false, 'whitelabel' => $this->company->account->isPaid() ? true : false,
'text_body' => str_replace(['$view_button','$viewButton','$view_link','$view_button'], '$view_url', $content), 'text_body' => str_replace(['$view_button','$viewButton','$view_link','$view_button'], '$view_url', $content),
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
} }
} }

View File

@ -152,6 +152,7 @@ class EntityFailedSendObject
"settings" => $settings, "settings" => $settings,
"whitelabel" => $this->company->account->isPaid() ? true : false, "whitelabel" => $this->company->account->isPaid() ? true : false,
"text_body" => str_replace("<br>", "\n", $content), "text_body" => str_replace("<br>", "\n", $content),
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
return $data; return $data;

View File

@ -12,6 +12,7 @@
namespace App\Mail\Admin; namespace App\Mail\Admin;
use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailable;
//@deprecated? //@deprecated?
class EntityNotificationMailer extends Mailable class EntityNotificationMailer extends Mailable
{ {

View File

@ -105,6 +105,7 @@ class EntityPaidObject
'settings' => $settings, 'settings' => $settings,
'whitelabel' => $this->company->account->isPaid() ? true : false, 'whitelabel' => $this->company->account->isPaid() ? true : false,
'text_body' => $content, 'text_body' => $content,
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
return $data; return $data;

View File

@ -87,6 +87,8 @@ class EntitySentObject
'logo' => $this->company->present()->logo(), 'logo' => $this->company->present()->logo(),
'settings' => $this->company->settings, 'settings' => $this->company->settings,
'whitelabel' => $this->company->account->isPaid() ? true : false, 'whitelabel' => $this->company->account->isPaid() ? true : false,
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
$mail_obj->markdown = 'email.admin.generic'; $mail_obj->markdown = 'email.admin.generic';
$mail_obj->tag = $this->company->company_key; $mail_obj->tag = $this->company->company_key;
@ -197,6 +199,8 @@ class EntitySentObject
'settings' => $settings, 'settings' => $settings,
'whitelabel' => $this->company->account->isPaid() ? true : false, 'whitelabel' => $this->company->account->isPaid() ? true : false,
'text_body' => $content, 'text_body' => $content,
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
} }
} }

View File

@ -116,6 +116,7 @@ class EntityViewedObject
'settings' => $settings, 'settings' => $settings,
'whitelabel' => $this->company->account->isPaid() ? true : false, 'whitelabel' => $this->company->account->isPaid() ? true : false,
'text_body' => $content, 'text_body' => $content,
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
return $data; return $data;

View File

@ -76,6 +76,7 @@ class InventoryNotificationObject
'settings' => $this->product->company->settings, 'settings' => $this->product->company->settings,
'whitelabel' => $this->product->company->account->isPaid() ? true : false, 'whitelabel' => $this->product->company->account->isPaid() ? true : false,
'text_body' => $content, 'text_body' => $content,
'template' => $this->product->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
return $data; return $data;

View File

@ -99,6 +99,7 @@ class PaymentFailureObject
'button' => $this->use_react_url ? ctrans('texts.view_client') : ctrans('texts.login'), 'button' => $this->use_react_url ? ctrans('texts.view_client') : ctrans('texts.login'),
'additional_info' => $this->error, 'additional_info' => $this->error,
'text_body' => $content, 'text_body' => $content,
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
return $data; return $data;

View File

@ -92,6 +92,7 @@ class PurchaseOrderAcceptedObject
'settings' => $settings, 'settings' => $settings,
'whitelabel' => $this->company->account->isPaid() ? true : false, 'whitelabel' => $this->company->account->isPaid() ? true : false,
'text_body' => $content, 'text_body' => $content,
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
return $data; return $data;

View File

@ -91,6 +91,7 @@ class QuoteApprovedObject
'settings' => $settings, 'settings' => $settings,
'whitelabel' => $this->company->account->isPaid() ? true : false, 'whitelabel' => $this->company->account->isPaid() ? true : false,
'text_body' => $content, 'text_body' => $content,
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
return $data; return $data;

View File

@ -91,6 +91,8 @@ class QuoteExpiredObject
'settings' => $settings, 'settings' => $settings,
'whitelabel' => $this->company->account->isPaid() ? true : false, 'whitelabel' => $this->company->account->isPaid() ? true : false,
'text_body' => $content, 'text_body' => $content,
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
return $data; return $data;

View File

@ -41,6 +41,7 @@ class ResetPasswordObject
'settings' => $this->company->settings, 'settings' => $this->company->settings,
'logo' => $this->company->present()->logo(), 'logo' => $this->company->present()->logo(),
'text_body' => ctrans('texts.reset_password'), 'text_body' => ctrans('texts.reset_password'),
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
$mail_obj = new \stdClass(); $mail_obj = new \stdClass();

View File

@ -53,6 +53,7 @@ class VerifyUserObject
'logo' => $this->company->present()->logo(), 'logo' => $this->company->present()->logo(),
'signature' => $this->company->settings->email_signature, 'signature' => $this->company->settings->email_signature,
'text_body' => ctrans('texts.confirmation_message'), 'text_body' => ctrans('texts.confirmation_message'),
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
$mail_obj = new \stdClass(); $mail_obj = new \stdClass();

View File

@ -52,7 +52,7 @@ class ClientStatement extends Mailable
public function content() public function content()
{ {
return new Content( return new Content(
view: 'email.template.client', view: $this->data['company']->account->isPremium() ? 'email.template.client_premium' : 'email.template.client',
text: 'email.template.text', text: 'email.template.text',
with: [ with: [
'text_body' => $this->data['body'], 'text_body' => $this->data['body'],

View File

@ -48,6 +48,7 @@ class ClientContactRequestCancellationObject
'signature' => $this->company->settings->email_signature, 'signature' => $this->company->settings->email_signature,
'settings' => $this->company->settings, 'settings' => $this->company->settings,
'logo' => $this->company->present()->logo(), 'logo' => $this->company->present()->logo(),
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]; ];
$mail_obj = new \stdClass(); $mail_obj = new \stdClass();

View File

@ -60,6 +60,7 @@ class OtpCode extends Mailable
'whitelabel' => $this->company->account->isPaid(), 'whitelabel' => $this->company->account->isPaid(),
'url' => 'xx', 'url' => 'xx',
'button' => false, 'button' => false,
'template' => $this->company->account->isPremium() ? 'email.template.admin_premium' : 'email.template.admin',
]); ]);
} }
} }

View File

@ -75,7 +75,7 @@ class TemplateEmail extends Mailable
$template_name = 'email.template.'.$this->build_email->getTemplate(); $template_name = 'email.template.'.$this->build_email->getTemplate();
if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') { if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') {
$template_name = 'email.template.client'; $template_name = $this->company->account->isPremium() ? 'email.template.client_premium' : 'email.template.client';
} }
if ($this->build_email->getTemplate() == 'custom') { if ($this->build_email->getTemplate() == 'custom') {

View File

@ -19,7 +19,6 @@ use Illuminate\Support\Facades\App;
class UserLoggedIn extends Mailable class UserLoggedIn extends Mailable
{ {
/** /**
* Create a new message instance. * Create a new message instance.
* *

View File

@ -72,7 +72,7 @@ class VendorTemplateEmail extends Mailable
$template_name = 'email.template.'.$this->build_email->getTemplate(); $template_name = 'email.template.'.$this->build_email->getTemplate();
if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') { if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') {
$template_name = 'email.template.client'; $template_name = $this->company->account->isPremium() ? 'email.template.client_premium' : 'email.template.client';
} }
if ($this->build_email->getTemplate() == 'custom') { if ($this->build_email->getTemplate() == 'custom') {

View File

@ -296,6 +296,7 @@ class Account extends BaseModel
public function isPremium(): bool public function isPremium(): bool
{ {
// return true;
return Ninja::isHosted() && $this->isPaidHostedClient() && !$this->isTrial() && Carbon::createFromTimestamp($this->created_at)->diffInMonths() > 2; return Ninja::isHosted() && $this->isPaidHostedClient() && !$this->isTrial() && Carbon::createFromTimestamp($this->created_at)->diffInMonths() > 2;
} }

View File

@ -328,8 +328,9 @@ class BaseModel extends Model
*/ */
public function parseHtmlVariables(string $field, array $variables): string public function parseHtmlVariables(string $field, array $variables): string
{ {
if(!$this->{$field}) if(!$this->{$field}) {
return ''; return '';
}
$section = strtr($this->{$field}, $variables['labels']); $section = strtr($this->{$field}, $variables['labels']);

View File

@ -112,6 +112,13 @@ use Laracasts\Presenter\PresentableTrait;
* @property int $notify_vendor_when_paid * @property int $notify_vendor_when_paid
* @property int $invoice_task_hours * @property int $invoice_task_hours
* @property int $deleted_at * @property int $deleted_at
* @property string $smtp_username
* @property string $smtp_password
* @property string $smtp_host
* @property string $smtp_port
* @property string $smtp_encryption
* @property string $smtp_local_domain
* @property boolean $smtp_verify_peer
* @property-read \App\Models\Account $account * @property-read \App\Models\Account $account
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read int|null $activities_count * @property-read int|null $activities_count
@ -352,12 +359,19 @@ class Company extends BaseModel
'calculate_taxes', 'calculate_taxes',
'tax_data', 'tax_data',
'e_invoice_certificate_passphrase', 'e_invoice_certificate_passphrase',
'smtp_host',
'smtp_port',
'smtp_encryption',
'smtp_local_domain',
'smtp_verify_peer',
]; ];
protected $hidden = [ protected $hidden = [
'id', 'id',
'db', 'db',
'ip', 'ip',
'smtp_username',
'smtp_password',
]; ];
protected $casts = [ protected $casts = [
@ -372,6 +386,8 @@ class Company extends BaseModel
'tax_data' => 'object', 'tax_data' => 'object',
'origin_tax_data' => 'object', 'origin_tax_data' => 'object',
'e_invoice_certificate_passphrase' => EncryptedCast::class, 'e_invoice_certificate_passphrase' => EncryptedCast::class,
'smtp_username' => 'encrypted',
'smtp_password' => 'encrypted',
]; ];
protected $with = []; protected $with = [];

View File

@ -201,7 +201,8 @@ class CompanyUser extends Pivot
* @return bool * @return bool
*/ */
public function portalType(): bool public function portalType(): bool
{ nlog(isset($this->react_settings->react_notification_link) && $this->react_settings->react_notification_link); {
nlog(isset($this->react_settings->react_notification_link) && $this->react_settings->react_notification_link);
return isset($this->react_settings->react_notification_link) && $this->react_settings->react_notification_link; return isset($this->react_settings->react_notification_link) && $this->react_settings->react_notification_link;
} }

View File

@ -176,7 +176,11 @@ class Document extends BaseModel
public function generateRoute($absolute = false) public function generateRoute($absolute = false)
{ {
try{
return route('api.documents.show', ['document' => $this->hashed_id]).'/download'; return route('api.documents.show', ['document' => $this->hashed_id]).'/download';
}catch(\Exception $e){
return '';
}
} }
public function deleteFile() public function deleteFile()

View File

@ -17,7 +17,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* App\Models\License * App\Models\License
* *
* @property int $id * @property int $id
* @property int|null $created_at * @property \Carbon\Carbon $created_at
* @property int|null $updated_at * @property int|null $updated_at
* @property int|null $deleted_at * @property int|null $deleted_at
* @property string|null $first_name * @property string|null $first_name
@ -28,6 +28,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property string|null $transaction_reference * @property string|null $transaction_reference
* @property int|null $product_id * @property int|null $product_id
* @property int|null $recurring_invoice_id * @property int|null $recurring_invoice_id
* @property-read \App\Models\RecurringInvoice $recurring_invoice
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel company() * @method static \Illuminate\Database\Eloquent\Builder|StaticModel company()
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel exclude($columns) * @method static \Illuminate\Database\Eloquent\Builder|StaticModel exclude($columns)
* @method static \Illuminate\Database\Eloquent\Builder|License newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|License newModelQuery()
@ -53,4 +54,24 @@ use Illuminate\Database\Eloquent\SoftDeletes;
class License extends StaticModel class License extends StaticModel
{ {
use SoftDeletes; use SoftDeletes;
protected $casts = [
'created_at' => 'date',
];
public function expiry(): string
{
return $this->created_at->addYear()->format('Y-m-d');
}
public function recurring_invoice()
{
return $this->belongsTo(RecurringInvoice::class);
}
public function url()
{
$contact = $this->recurring_invoice->client->contacts()->where('email', $this->email)->first();
}
} }

View File

@ -325,8 +325,9 @@ class Payment extends BaseModel
return '<h6><span class="badge badge-danger">'.ctrans('texts.payment_status_3').'</span></h6>'; return '<h6><span class="badge badge-danger">'.ctrans('texts.payment_status_3').'</span></h6>';
case self::STATUS_COMPLETED: case self::STATUS_COMPLETED:
if($this->amount > $this->applied) if($this->amount > $this->applied) {
return '<h6><span class="badge badge-info">' . ctrans('texts.partially_unapplied') . '</span></h6>'; return '<h6><span class="badge badge-info">' . ctrans('texts.partially_unapplied') . '</span></h6>';
}
return '<h6><span class="badge badge-info">'.ctrans('texts.payment_status_4').'</span></h6>'; return '<h6><span class="badge badge-info">'.ctrans('texts.payment_status_4').'</span></h6>';
case self::STATUS_PARTIALLY_REFUNDED: case self::STATUS_PARTIALLY_REFUNDED:

View File

@ -75,6 +75,10 @@ class Project extends BaseModel
'number', 'number',
]; ];
protected $with = [
'documents',
];
public function getEntityType() public function getEntityType()
{ {
return self::class; return self::class;

View File

@ -78,7 +78,6 @@ use Illuminate\Support\Carbon;
* @property-read \App\Models\User $user * @property-read \App\Models\User $user
* @property-read \App\Models\Vendor|null $vendor * @property-read \App\Models\Vendor|null $vendor
* @property-read \App\Models\ExpenseCategory|null $category * @property-read \App\Models\ExpenseCategory|null $category
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel company()
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel exclude($columns) * @method static \Illuminate\Database\Eloquent\Builder|BaseModel exclude($columns)
* @method static \Database\Factories\RecurringExpenseFactory factory($count = null, $state = []) * @method static \Database\Factories\RecurringExpenseFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder|RecurringExpense filter(\App\Filters\QueryFilters $filters) * @method static \Illuminate\Database\Eloquent\Builder|RecurringExpense filter(\App\Filters\QueryFilters $filters)
@ -222,7 +221,7 @@ class RecurringExpense extends BaseModel
return $this->belongsTo(User::class, 'assigned_user_id', 'id'); return $this->belongsTo(User::class, 'assigned_user_id', 'id');
} }
public function company() public function company():\Illuminate\Database\Eloquent\Relations\BelongsTo
{ {
return $this->belongsTo(Company::class); return $this->belongsTo(Company::class);
} }

View File

@ -106,7 +106,6 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \App\Models\Subscription|null $subscription * @property-read \App\Models\Subscription|null $subscription
* @property-read \App\Models\User $user * @property-read \App\Models\User $user
* @property-read \App\Models\Vendor|null $vendor * @property-read \App\Models\Vendor|null $vendor
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel company()
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel exclude($columns) * @method static \Illuminate\Database\Eloquent\Builder|BaseModel exclude($columns)
* @method static \Database\Factories\RecurringInvoiceFactory factory($count = null, $state = []) * @method static \Database\Factories\RecurringInvoiceFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder|RecurringInvoice filter(\App\Filters\QueryFilters $filters) * @method static \Illuminate\Database\Eloquent\Builder|RecurringInvoice filter(\App\Filters\QueryFilters $filters)
@ -343,16 +342,25 @@ class RecurringInvoice extends BaseModel
} }
} }
public function calculateStatus() /**
* CalculateStatus
*
* Calculates the status of the Recurring Invoice.
*
* We only apply the pending status on new models, we never revert an invoice back to
* pending.
* @param bool $new_model
* @return int
*/
public function calculateStatus(bool $new_model = false) //15-02-2024 - $new_model needed
{ {
if($this->remaining_cycles == 0) { if($this->remaining_cycles == 0) {
return self::STATUS_COMPLETED; return self::STATUS_COMPLETED;
} elseif ($this->status_id == self::STATUS_ACTIVE && Carbon::parse($this->next_send_date)->isFuture()) { } elseif ($new_model && $this->status_id == self::STATUS_ACTIVE && Carbon::parse($this->next_send_date)->isFuture())
return self::STATUS_PENDING; return self::STATUS_PENDING;
} else {
return $this->status_id; return $this->status_id;
}
} }

View File

@ -131,6 +131,10 @@ class Task extends BaseModel
'deleted_at' => 'timestamp', 'deleted_at' => 'timestamp',
]; ];
protected $with = [
// 'project',
];
protected $touches = []; protected $touches = [];
public function getEntityType() public function getEntityType()

View File

@ -214,8 +214,9 @@ class CheckoutComPaymentDriver extends BaseDriver
{ {
$this->init(); $this->init();
if($this->company_gateway->update_details) if($this->company_gateway->update_details) {
$this->updateCustomer(); $this->updateCustomer();
}
$request = new RefundRequest(); $request = new RefundRequest();
$request->reference = "{$payment->transaction_reference} ".now(); $request->reference = "{$payment->transaction_reference} ".now();
@ -332,8 +333,9 @@ class CheckoutComPaymentDriver extends BaseDriver
public function updateCustomer($customer_id = null) public function updateCustomer($customer_id = null)
{ {
if(!$customer_id) if(!$customer_id) {
return; return;
}
try { try {

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