mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge pull request #9266 from turbo124/v5-develop
Improve Recurring Expense Filters
This commit is contained in:
commit
814ecb3387
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:
|
||||
|
@ -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.
|
||||
*
|
||||
@ -63,9 +140,33 @@ class RecurringExpenseFilters 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])
|
||||
->orderBy(\App\Models\Client::select('name')
|
||||
->whereColumn('clients.id', 'expenses.client_id'), $sort_col[1]);
|
||||
}
|
||||
|
||||
return $this->builder->orderBy($sort_col[0], $dir);
|
||||
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 (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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,6 @@ class TaskTransformer extends BaseTransformer
|
||||
|
||||
$time_log = collect($task_items_data)
|
||||
->map(function ($item) {
|
||||
|
||||
return $this->parseLog($item);
|
||||
|
||||
})->toJson();
|
||||
@ -80,10 +79,10 @@ class TaskTransformer extends BaseTransformer
|
||||
|
||||
$notes = $item['task.notes'] ?? '';
|
||||
|
||||
if(isset($item['task.is_billable']) && is_string($item['task.is_billable']) && in_array($item['task.is_billable'], ['yes', 'true', '1'])) {
|
||||
if(isset($item['task.billable']) && is_string($item['task.billable']) && in_array($item['task.billable'], ['yes', 'true', '1', 'TRUE', 'YES'])) {
|
||||
$is_billable = true;
|
||||
} elseif(isset($item['task.is_billable']) && is_bool($item['task.is_billable'])) {
|
||||
$is_billable = $item['task.is_billable'];
|
||||
} elseif(isset($item['task.billable']) && is_bool($item['task.billable'])) {
|
||||
$is_billable = $item['task.billable'];
|
||||
} else {
|
||||
$is_billable = false;
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Hyvor\JsonExporter\File;
|
||||
|
||||
class CompanyExport implements ShouldQueue
|
||||
{
|
||||
@ -46,8 +47,8 @@ class CompanyExport implements ShouldQueue
|
||||
private $export_format = 'json';
|
||||
|
||||
private $export_data = [];
|
||||
|
||||
|
||||
private $writer;
|
||||
private $file_name;
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
@ -67,12 +68,16 @@ class CompanyExport implements ShouldQueue
|
||||
public function handle()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
nlog("starting export");
|
||||
|
||||
|
||||
$this->file_name = date('Y-m-d') . '_' . str_replace([" ", "/"], ["_",""], $this->company->present()->name() . '_' . $this->company->company_key . '.json');
|
||||
|
||||
$this->writer = new File($this->file_name);
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
$this->export_data['app_version'] = config('ninja.app_version');
|
||||
$this->export_data['storage_url'] = Storage::url('');
|
||||
$this->writer->value('app_version', config('ninja.app_version'));
|
||||
$this->writer->value('storage_url', Storage::url(''));
|
||||
|
||||
$this->export_data['activities'] = $this->company->all_activities->map(function ($activity) {
|
||||
$activity = $this->transformArrayOfKeys($activity, [
|
||||
@ -98,27 +103,21 @@ class CompanyExport implements ShouldQueue
|
||||
return $activity;
|
||||
})->makeHidden(['id'])->all();
|
||||
|
||||
// $this->export_data['backups'] = $this->company->all_activities()->with('backup')->cursor()->map(function ($activity){
|
||||
|
||||
// $backup = $activity->backup;
|
||||
$x = $this->writer->collection('activities');
|
||||
$x->addItems($this->export_data['activities']);
|
||||
$this->export_data = null;
|
||||
|
||||
// if(!$backup)
|
||||
// return;
|
||||
|
||||
// $backup->activity_id = $this->encodePrimaryKey($backup->activity_id);
|
||||
|
||||
// return $backup;
|
||||
|
||||
// })->all();
|
||||
|
||||
$this->export_data['users'] = $this->company->users()->withTrashed()->cursor()->map(function ($user) {
|
||||
$user->account_id = $this->encodePrimaryKey($user->account_id);
|
||||
// $user->id = $this->encodePrimaryKey($user->id);
|
||||
|
||||
return $user->makeVisible(['id']);
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('users');
|
||||
$x->addItems($this->export_data['users']);
|
||||
$this->export_data = null;
|
||||
|
||||
$this->export_data['client_contacts'] = $this->company->client_contacts->map(function ($client_contact) {
|
||||
$client_contact = $this->transformArrayOfKeys($client_contact, ['company_id', 'user_id', 'client_id']);
|
||||
|
||||
@ -138,6 +137,10 @@ class CompanyExport implements ShouldQueue
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('client_contacts');
|
||||
$x->addItems($this->export_data['client_contacts']);
|
||||
$this->export_data = null;
|
||||
|
||||
$this->export_data['client_gateway_tokens'] = $this->company->client_gateway_tokens->map(function ($client_gateway_token) {
|
||||
$client_gateway_token = $this->transformArrayOfKeys($client_gateway_token, ['company_id', 'client_id', 'company_gateway_id']);
|
||||
|
||||
@ -145,6 +148,10 @@ class CompanyExport implements ShouldQueue
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('client_gateway_tokens');
|
||||
$x->addItems($this->export_data['client_gateway_tokens']);
|
||||
$this->export_data = null;
|
||||
|
||||
$this->export_data['clients'] = $this->company->clients()->orderBy('number', 'DESC')->cursor()->map(function ($client) {
|
||||
$client = $this->transformArrayOfKeys($client, ['company_id', 'user_id', 'assigned_user_id', 'group_settings_id']);
|
||||
$client->tax_data = '';
|
||||
@ -152,9 +159,20 @@ class CompanyExport implements ShouldQueue
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('clients');
|
||||
$x->addItems($this->export_data['clients']);
|
||||
$this->export_data = null;
|
||||
|
||||
$this->export_data['company'] = $this->company->toArray();
|
||||
$this->export_data['company']['company_key'] = $this->createHash();
|
||||
|
||||
|
||||
|
||||
$x = $this->writer->collection('company');
|
||||
$x->addItems($this->export_data['company']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['company_gateways'] = $this->company->company_gateways()->withTrashed()->cursor()->map(function ($company_gateway) {
|
||||
$company_gateway = $this->transformArrayOfKeys($company_gateway, ['company_id', 'user_id']);
|
||||
$company_gateway->config = decrypt($company_gateway->config);
|
||||
@ -162,25 +180,49 @@ class CompanyExport implements ShouldQueue
|
||||
return $company_gateway->makeVisible(['id']);
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('company_gateways');
|
||||
$x->addItems($this->export_data['company_gateways']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
|
||||
|
||||
$this->export_data['company_tokens'] = $this->company->tokens->map(function ($token) {
|
||||
$token = $this->transformArrayOfKeys($token, ['company_id', 'account_id', 'user_id']);
|
||||
|
||||
return $token;
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('company_tokens');
|
||||
$x->addItems($this->export_data['company_tokens']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['company_ledger'] = $this->company->ledger->map(function ($ledger) {
|
||||
$ledger = $this->transformArrayOfKeys($ledger, ['activity_id', 'client_id', 'company_id', 'account_id', 'user_id','company_ledgerable_id']);
|
||||
|
||||
return $ledger;
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('company_ledger');
|
||||
$x->addItems($this->export_data['company_ledger']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['company_users'] = $this->company->company_users()->without(['user','account'])->cursor()->map(function ($company_user) {
|
||||
$company_user = $this->transformArrayOfKeys($company_user, ['company_id', 'account_id', 'user_id']);
|
||||
|
||||
// return $company_user->makeHidden(['react_settings']);
|
||||
return $company_user;
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('company_users');
|
||||
$x->addItems($this->export_data['company_users']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['credits'] = $this->company->credits()->orderBy('number', 'DESC')->cursor()->map(function ($credit) {
|
||||
$credit = $this->transformBasicEntities($credit);
|
||||
$credit = $this->transformArrayOfKeys($credit, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id','invoice_id']);
|
||||
@ -188,6 +230,10 @@ class CompanyExport implements ShouldQueue
|
||||
return $credit->makeVisible(['id']);
|
||||
})->all();
|
||||
|
||||
$x = $this->writer->collection('credits');
|
||||
$x->addItems($this->export_data['credits']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['credit_invitations'] = CreditInvitation::query()->where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($credit) {
|
||||
$credit = $this->transformArrayOfKeys($credit, ['company_id', 'user_id', 'client_contact_id', 'credit_id']);
|
||||
@ -195,8 +241,20 @@ class CompanyExport implements ShouldQueue
|
||||
return $credit->makeVisible(['id']);
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('credit_invitations');
|
||||
$x->addItems($this->export_data['credit_invitations']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['designs'] = $this->company->user_designs->makeHidden(['id'])->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('designs');
|
||||
$x->addItems($this->export_data['designs']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['documents'] = $this->company->all_documents->map(function ($document) {
|
||||
$document = $this->transformArrayOfKeys($document, ['user_id', 'assigned_user_id', 'company_id', 'project_id', 'vendor_id','documentable_id']);
|
||||
$document->hashed_id = $this->encodePrimaryKey($document->id);
|
||||
@ -204,12 +262,20 @@ class CompanyExport implements ShouldQueue
|
||||
return $document->makeVisible(['id']);
|
||||
})->all();
|
||||
|
||||
$x = $this->writer->collection('documents');
|
||||
$x->addItems($this->export_data['documents']);
|
||||
$this->export_data = null;
|
||||
|
||||
$this->export_data['expense_categories'] = $this->company->expense_categories()->cursor()->map(function ($expense_category) {
|
||||
$expense_category = $this->transformArrayOfKeys($expense_category, ['user_id', 'company_id']);
|
||||
|
||||
return $expense_category->makeVisible(['id']);
|
||||
})->all();
|
||||
|
||||
$x = $this->writer->collection('expense_categories');
|
||||
$x->addItems($this->export_data['expense_categories']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['expenses'] = $this->company->expenses()->orderBy('number', 'DESC')->cursor()->map(function ($expense) {
|
||||
$expense = $this->transformBasicEntities($expense);
|
||||
@ -218,6 +284,12 @@ class CompanyExport implements ShouldQueue
|
||||
return $expense->makeVisible(['id']);
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('expenses');
|
||||
$x->addItems($this->export_data['expenses']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['group_settings'] = $this->company->group_settings->map(function ($gs) {
|
||||
$gs = $this->transformArrayOfKeys($gs, ['user_id', 'company_id']);
|
||||
|
||||
@ -225,6 +297,11 @@ class CompanyExport implements ShouldQueue
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('group_settings');
|
||||
$x->addItems($this->export_data['group_settings']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['invoices'] = $this->company->invoices()->orderBy('number', 'DESC')->cursor()->map(function ($invoice) {
|
||||
$invoice = $this->transformBasicEntities($invoice);
|
||||
$invoice = $this->transformArrayOfKeys($invoice, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
|
||||
@ -238,12 +315,22 @@ class CompanyExport implements ShouldQueue
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('invoices');
|
||||
$x->addItems($this->export_data['invoices']);
|
||||
$this->export_data = null;
|
||||
|
||||
$this->export_data['invoice_invitations'] = InvoiceInvitation::query()->where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($invoice) {
|
||||
$invoice = $this->transformArrayOfKeys($invoice, ['company_id', 'user_id', 'client_contact_id', 'invoice_id']);
|
||||
|
||||
return $invoice->makeVisible(['id']);
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('invoice_invitations');
|
||||
$x->addItems($this->export_data['invoice_invitations']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['payment_terms'] = $this->company->user_payment_terms->map(function ($term) {
|
||||
$term = $this->transformArrayOfKeys($term, ['user_id', 'company_id']);
|
||||
|
||||
@ -251,6 +338,11 @@ class CompanyExport implements ShouldQueue
|
||||
})->makeHidden(['id'])->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('payment_terms');
|
||||
$x->addItems($this->export_data['payment_terms']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['payments'] = $this->company->payments()->orderBy('number', 'DESC')->cursor()->map(function ($payment) {
|
||||
$payment = $this->transformBasicEntities($payment);
|
||||
$payment = $this->transformArrayOfKeys($payment, ['client_id','project_id', 'vendor_id', 'client_contact_id', 'invitation_id', 'company_gateway_id', 'transaction_id']);
|
||||
@ -260,6 +352,13 @@ class CompanyExport implements ShouldQueue
|
||||
return $payment->makeVisible(['id']);
|
||||
})->all();
|
||||
|
||||
|
||||
|
||||
$x = $this->writer->collection('payments');
|
||||
$x->addItems($this->export_data['payments']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['products'] = $this->company->products->map(function ($product) {
|
||||
$product = $this->transformBasicEntities($product);
|
||||
$product = $this->transformArrayOfKeys($product, ['vendor_id','project_id']);
|
||||
@ -267,6 +366,12 @@ class CompanyExport implements ShouldQueue
|
||||
return $product->makeVisible(['id']);
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('products');
|
||||
$x->addItems($this->export_data['products']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['projects'] = $this->company->projects()->orderBy('number', 'DESC')->cursor()->map(function ($project) {
|
||||
$project = $this->transformBasicEntities($project);
|
||||
$project = $this->transformArrayOfKeys($project, ['client_id']);
|
||||
@ -274,6 +379,12 @@ class CompanyExport implements ShouldQueue
|
||||
return $project->makeVisible(['id']);
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('projects');
|
||||
$x->addItems($this->export_data['projects']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['quotes'] = $this->company->quotes()->orderBy('number', 'DESC')->cursor()->map(function ($quote) {
|
||||
$quote = $this->transformBasicEntities($quote);
|
||||
$quote = $this->transformArrayOfKeys($quote, ['invoice_id','recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
|
||||
@ -282,12 +393,23 @@ class CompanyExport implements ShouldQueue
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('quotes');
|
||||
$x->addItems($this->export_data['quotes']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['quote_invitations'] = QuoteInvitation::query()->where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($quote) {
|
||||
$quote = $this->transformArrayOfKeys($quote, ['company_id', 'user_id', 'client_contact_id', 'quote_id']);
|
||||
|
||||
return $quote->makeVisible(['id']);
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('quote_invitations');
|
||||
$x->addItems($this->export_data['quote_invitations']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['recurring_expenses'] = $this->company->recurring_expenses()->orderBy('number', 'DESC')->cursor()->map(function ($expense) {
|
||||
$expense = $this->transformBasicEntities($expense);
|
||||
$expense = $this->transformArrayOfKeys($expense, ['vendor_id', 'invoice_id', 'client_id', 'category_id', 'project_id']);
|
||||
@ -295,6 +417,13 @@ class CompanyExport implements ShouldQueue
|
||||
return $expense->makeVisible(['id']);
|
||||
})->all();
|
||||
|
||||
|
||||
|
||||
$x = $this->writer->collection('recurring_expenses');
|
||||
$x->addItems($this->export_data['recurring_expenses']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['recurring_invoices'] = $this->company->recurring_invoices()->orderBy('number', 'DESC')->cursor()->map(function ($ri) {
|
||||
$ri = $this->transformBasicEntities($ri);
|
||||
$ri = $this->transformArrayOfKeys($ri, ['client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
|
||||
@ -303,12 +432,24 @@ class CompanyExport implements ShouldQueue
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('recurring_invoices');
|
||||
$x->addItems($this->export_data['recurring_invoices']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['recurring_invoice_invitations'] = RecurringInvoiceInvitation::query()->where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($ri) {
|
||||
$ri = $this->transformArrayOfKeys($ri, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']);
|
||||
|
||||
return $ri;
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('recurring_invoice_invitations');
|
||||
$x->addItems($this->export_data['recurring_invoice_invitations']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
|
||||
$this->export_data['subscriptions'] = $this->company->subscriptions->map(function ($subscription) {
|
||||
$subscription = $this->transformBasicEntities($subscription);
|
||||
$subscription->group_id = $this->encodePrimaryKey($subscription->group_id);
|
||||
@ -323,6 +464,10 @@ class CompanyExport implements ShouldQueue
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('subscriptions');
|
||||
$x->addItems($this->export_data['subscriptions']);
|
||||
$this->export_data = null;
|
||||
|
||||
$this->export_data['system_logs'] = $this->company->system_logs->map(function ($log) {
|
||||
$log->client_id = $this->encodePrimaryKey($log->client_id);
|
||||
$log->company_id = $this->encodePrimaryKey($log->company_id);
|
||||
@ -330,14 +475,26 @@ class CompanyExport implements ShouldQueue
|
||||
return $log;
|
||||
})->makeHidden(['id'])->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('system_logs');
|
||||
$x->addItems($this->export_data['system_logs']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['tasks'] = $this->company->tasks()->orderBy('number', 'DESC')->cursor()->map(function ($task) {
|
||||
$task = $this->transformBasicEntities($task);
|
||||
$task = $this->transformArrayOfKeys($task, ['client_id', 'invoice_id', 'project_id', 'status_id']);
|
||||
|
||||
return $task->makeHidden(['hash','meta'])->makeVisible(['id']);
|
||||
// return $task->makeHidden(['hash','meta'])->makeVisible(['id']); //@release v5.7.63
|
||||
})->all();
|
||||
|
||||
|
||||
|
||||
$x = $this->writer->collection('tasks');
|
||||
$x->addItems($this->export_data['tasks']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['task_statuses'] = $this->company->task_statuses->map(function ($status) {
|
||||
$status->id = $this->encodePrimaryKey($status->id);
|
||||
$status->user_id = $this->encodePrimaryKey($status->user_id);
|
||||
@ -346,6 +503,13 @@ class CompanyExport implements ShouldQueue
|
||||
return $status;
|
||||
})->all();
|
||||
|
||||
|
||||
|
||||
$x = $this->writer->collection('task_statuses');
|
||||
$x->addItems($this->export_data['task_statuses']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['tax_rates'] = $this->company->tax_rates->map(function ($rate) {
|
||||
$rate->company_id = $this->encodePrimaryKey($rate->company_id);
|
||||
$rate->user_id = $this->encodePrimaryKey($rate->user_id);
|
||||
@ -353,11 +517,24 @@ class CompanyExport implements ShouldQueue
|
||||
return $rate;
|
||||
})->makeHidden(['id'])->all();
|
||||
|
||||
|
||||
|
||||
$x = $this->writer->collection('tax_rates');
|
||||
$x->addItems($this->export_data['tax_rates']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['vendors'] = $this->company->vendors()->orderBy('number', 'DESC')->cursor()->map(function ($vendor) {
|
||||
return $this->transformBasicEntities($vendor)->makeVisible(['id']);
|
||||
})->all();
|
||||
|
||||
|
||||
|
||||
$x = $this->writer->collection('vendors');
|
||||
$x->addItems($this->export_data['vendors']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['vendor_contacts'] = VendorContact::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($vendor) {
|
||||
$vendor = $this->transformBasicEntities($vendor);
|
||||
$vendor = $this->transformArrayOfKeys($vendor, ['vendor_id']);
|
||||
@ -365,6 +542,13 @@ class CompanyExport implements ShouldQueue
|
||||
return $vendor->makeVisible(['id','user_id']);
|
||||
})->all();
|
||||
|
||||
|
||||
|
||||
$x = $this->writer->collection('vendor_contacts');
|
||||
$x->addItems($this->export_data['vendor_contacts']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['webhooks'] = $this->company->webhooks->map(function ($hook) {
|
||||
$hook->user_id = $this->encodePrimaryKey($hook->user_id);
|
||||
$hook->company_id = $this->encodePrimaryKey($hook->company_id);
|
||||
@ -372,6 +556,12 @@ class CompanyExport implements ShouldQueue
|
||||
return $hook;
|
||||
})->makeHidden(['id'])->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('webhooks');
|
||||
$x->addItems($this->export_data['webhooks']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
$this->export_data['purchase_orders'] = $this->company->purchase_orders()->orderBy('number', 'DESC')->cursor()->map(function ($purchase_order) {
|
||||
$purchase_order = $this->transformBasicEntities($purchase_order);
|
||||
$purchase_order = $this->transformArrayOfKeys($purchase_order, ['expense_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id','project_id']);
|
||||
@ -384,6 +574,13 @@ class CompanyExport implements ShouldQueue
|
||||
'company_id',]);
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('purchase_orders');
|
||||
$x->addItems($this->export_data['purchase_orders']);
|
||||
$this->export_data = null;
|
||||
|
||||
|
||||
|
||||
$this->export_data['purchase_order_invitations'] = PurchaseOrderInvitation::query()->where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($purchase_order) {
|
||||
$purchase_order = $this->transformArrayOfKeys($purchase_order, ['company_id', 'user_id', 'vendor_contact_id', 'purchase_order_id']);
|
||||
|
||||
@ -391,26 +588,46 @@ class CompanyExport implements ShouldQueue
|
||||
})->all();
|
||||
|
||||
|
||||
$x = $this->writer->collection('purchase_order_invitations');
|
||||
$x->addItems($this->export_data['purchase_order_invitations']);
|
||||
$this->export_data = null;
|
||||
|
||||
$this->export_data['bank_integrations'] = $this->company->bank_integrations()->withTrashed()->orderBy('id', 'ASC')->cursor()->map(function ($bank_integration) {
|
||||
$bank_integration = $this->transformArrayOfKeys($bank_integration, ['account_id','company_id', 'user_id']);
|
||||
|
||||
return $bank_integration->makeVisible(['id','user_id','company_id','account_id','hashed_id']);
|
||||
})->all();
|
||||
|
||||
$x = $this->writer->collection('bank_integrations');
|
||||
$x->addItems($this->export_data['bank_integrations']);
|
||||
$this->export_data = null;
|
||||
|
||||
$this->export_data['bank_transactions'] = $this->company->bank_transactions()->withTrashed()->orderBy('id', 'ASC')->cursor()->map(function ($bank_transaction) {
|
||||
$bank_transaction = $this->transformArrayOfKeys($bank_transaction, ['company_id', 'user_id','bank_integration_id','expense_id','ninja_category_id','vendor_id']);
|
||||
|
||||
return $bank_transaction->makeVisible(['id','user_id','company_id']);
|
||||
})->all();
|
||||
|
||||
$x = $this->writer->collection('bank_transactions');
|
||||
$x->addItems($this->export_data['bank_transactions']);
|
||||
$this->export_data = null;
|
||||
|
||||
$this->export_data['schedulers'] = $this->company->schedulers()->withTrashed()->orderBy('id', 'ASC')->cursor()->map(function ($scheduler) {
|
||||
$scheduler = $this->transformArrayOfKeys($scheduler, ['company_id', 'user_id']);
|
||||
|
||||
return $scheduler->makeVisible(['id','user_id','company_id']);
|
||||
})->all();
|
||||
|
||||
$x = $this->writer->collection('schedulers');
|
||||
$x->addItems($this->export_data['schedulers']);
|
||||
$this->export_data = null;
|
||||
|
||||
//write to tmp and email to owner();
|
||||
|
||||
|
||||
$this->writer->end();
|
||||
|
||||
|
||||
$this->zipAndSend();
|
||||
|
||||
return true;
|
||||
@ -446,38 +663,42 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
private function zipAndSend()
|
||||
{
|
||||
$file_name = date('Y-m-d').'_'.str_replace([" ", "/"], ["_",""], $this->company->present()->name() . '_' . $this->company->company_key .'.zip');
|
||||
// $file_name = date('Y-m-d').'_'.str_replace([" ", "/"], ["_",""], $this->company->present()->name() . '_' . $this->company->company_key .'.zip');
|
||||
|
||||
$path = 'backups';
|
||||
$zip_path = \Illuminate\Support\Str::ascii(str_replace(".json", ".zip", $this->file_name));
|
||||
// $path = 'backups';
|
||||
|
||||
// Storage::makeDirectory(storage_path('backups/'));
|
||||
|
||||
try {
|
||||
mkdir(storage_path('backups/'));
|
||||
} catch(\Exception $e) {
|
||||
nlog("could not create directory");
|
||||
}
|
||||
// try {
|
||||
// mkdir(storage_path('backups/'));
|
||||
// } catch(\Exception $e) {
|
||||
// nlog("could not create directory");
|
||||
// }
|
||||
|
||||
$zip_path = storage_path('backups/'.\Illuminate\Support\Str::ascii($file_name));
|
||||
// $zip_path = storage_path('backups/'.\Illuminate\Support\Str::ascii($file_name));
|
||||
$zip = new \ZipArchive();
|
||||
|
||||
if ($zip->open($zip_path, \ZipArchive::CREATE) !== true) {
|
||||
nlog("cannot open {$zip_path}");
|
||||
}
|
||||
|
||||
$zip->addFromString("backup.json", json_encode($this->export_data));
|
||||
$zip->addFile($this->file_name);
|
||||
$zip->renameName($this->file_name, 'backup.json');
|
||||
|
||||
// $zip->addFromString("backup.json", json_encode($this->export_data));
|
||||
$zip->close();
|
||||
|
||||
Storage::disk(config('filesystems.default'))->put('backups/'.$file_name, file_get_contents($zip_path));
|
||||
Storage::disk(config('filesystems.default'))->put('backups/'.str_replace(".json", ".zip",$this->file_name), file_get_contents($zip_path));
|
||||
|
||||
if(file_exists($zip_path)) {
|
||||
unlink($zip_path);
|
||||
}
|
||||
|
||||
if(Ninja::isSelfHost()) {
|
||||
$storage_path = 'backups/'.$file_name;
|
||||
$storage_path = 'backups/'.str_replace(".json", ".zip",$this->file_name);
|
||||
} else {
|
||||
$storage_path = Storage::disk(config('filesystems.default'))->path('backups/'.$file_name);
|
||||
$storage_path = Storage::disk(config('filesystems.default'))->path('backups/'.str_replace(".json", ".zip",$this->file_name));
|
||||
}
|
||||
|
||||
$url = Cache::get($this->hash);
|
||||
|
@ -272,13 +272,10 @@ class CompanyImport implements ShouldQueue
|
||||
nlog("Company ID = {$this->company->id}");
|
||||
nlog("file_location ID = {$this->file_location}");
|
||||
|
||||
// $this->backup_file = Cache::get($this->hash);
|
||||
|
||||
if (empty($this->file_location)) {
|
||||
throw new \Exception('No import data found, has the cache expired?');
|
||||
}
|
||||
|
||||
// $this->backup_file = json_decode(file_get_contents($this->file_location));
|
||||
$tmp_file = $this->unzipFile();
|
||||
|
||||
$this->file_path = $tmp_file;
|
||||
@ -315,6 +312,7 @@ class CompanyImport implements ShouldQueue
|
||||
}
|
||||
|
||||
unlink($tmp_file);
|
||||
unlink($this->file_location);
|
||||
}
|
||||
|
||||
//
|
||||
@ -342,19 +340,31 @@ class CompanyImport implements ShouldQueue
|
||||
$path = TempFile::filePath(Storage::disk(config('filesystems.default'))->get($this->file_location), basename($this->file_location));
|
||||
|
||||
$zip = new ZipArchive();
|
||||
$archive = $zip->open($path);
|
||||
|
||||
$res = $zip->open($path);
|
||||
$file_path = sys_get_temp_dir().'/'.sha1(microtime());
|
||||
|
||||
$zip->extractTo($file_path);
|
||||
$zip->close();
|
||||
$file_location = "{$file_path}/backup.json";
|
||||
if ($res === true) {
|
||||
echo "ok";
|
||||
$extraction_res = $zip->extractTo($file_path);
|
||||
|
||||
nlog($extraction_res);
|
||||
|
||||
$closer = $zip->close();
|
||||
nlog($closer);
|
||||
|
||||
} else {
|
||||
echo "failed, code: " . $res;
|
||||
}
|
||||
|
||||
$file_path = "{$file_path}/backup.json";
|
||||
|
||||
nlog($file_path);
|
||||
|
||||
if (! file_exists($file_path)) {
|
||||
throw new NonExistingMigrationFile('Backup file does not exist, or is corrupted.');
|
||||
}
|
||||
|
||||
return $file_location;
|
||||
return $file_path;
|
||||
}
|
||||
|
||||
|
||||
|
@ -260,11 +260,36 @@ class NinjaMailerJob implements ShouldQueue
|
||||
$t->replace(Ninja::transformTranslations($this->nmo->settings));
|
||||
|
||||
/** Force free/trials onto specific mail driver */
|
||||
if(Ninja::isHosted() && !$this->company->account->isPaid())
|
||||
{
|
||||
$this->mailer = 'mailgun';
|
||||
$this->setHostedMailgunMailer();
|
||||
return $this;
|
||||
// if(Ninja::isHosted() && !$this->company->account->isPaid())
|
||||
// {
|
||||
// $this->mailer = 'mailgun';
|
||||
// $this->setHostedMailgunMailer();
|
||||
// return $this;
|
||||
// }
|
||||
|
||||
if(Ninja::isHosted() && $this->company->account->isPaid() && $this->nmo->settings->email_sending_method == 'default') {
|
||||
//check if outlook.
|
||||
|
||||
try{
|
||||
$email = $this->nmo->to_user->email;
|
||||
$domain = explode("@", $email)[1] ?? "";
|
||||
$dns = dns_get_record($domain, DNS_MX);
|
||||
$server = $dns[0]["target"];
|
||||
if(stripos($server, "outlook.com") !== false){
|
||||
|
||||
$this->mailer = 'postmark';
|
||||
$this->client_postmark_secret = config('services.postmark-outlook.token');
|
||||
|
||||
$this->nmo
|
||||
->mailable
|
||||
->from('maildelivery@invoice.services', 'Invoice Ninja');
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
catch(\Exception $e){
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
switch ($this->nmo->settings->email_sending_method) {
|
||||
|
@ -45,6 +45,7 @@ class QuoteCreatedNotification implements ShouldQueue
|
||||
|
||||
|
||||
/* We loop through each user and determine whether they need to be notified */
|
||||
/** @var \App\Models\CompanyUser $company_user */
|
||||
foreach ($event->company->company_users as $company_user) {
|
||||
/* The User */
|
||||
$user = $company_user->user;
|
||||
|
@ -201,7 +201,7 @@ class CompanyUser extends Pivot
|
||||
* @return bool
|
||||
*/
|
||||
public function portalType(): bool
|
||||
{
|
||||
{ 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;
|
||||
}
|
||||
|
||||
|
@ -204,7 +204,7 @@ class Credit extends BaseModel
|
||||
|
||||
public function getDueDateAttribute($value)
|
||||
{
|
||||
return $this->dateMutator($value);
|
||||
return $value ? $this->dateMutator($value) : null;
|
||||
}
|
||||
|
||||
public function getPartialDueDateAttribute($value)
|
||||
|
@ -220,7 +220,7 @@ class Document extends BaseModel
|
||||
$img = new \Imagick();
|
||||
$img->readImageBlob($file);
|
||||
$img->setImageCompression(true);
|
||||
$img->setImageCompressionQuality(50);
|
||||
$img->setImageCompressionQuality(40);
|
||||
|
||||
return $img->getImageBlob();
|
||||
|
||||
|
@ -486,10 +486,34 @@ class Email implements ShouldQueue
|
||||
{
|
||||
|
||||
/** Force free/trials onto specific mail driver */
|
||||
if(Ninja::isHosted() && !$this->company->account->isPaid()) {
|
||||
$this->mailer = 'mailgun';
|
||||
$this->setHostedMailgunMailer();
|
||||
return $this;
|
||||
// if(Ninja::isHosted() && !$this->company->account->isPaid()) {
|
||||
// $this->mailer = 'mailgun';
|
||||
// $this->setHostedMailgunMailer();
|
||||
// return $this;
|
||||
// }
|
||||
|
||||
if(Ninja::isHosted() && $this->company->account->isPaid() && $this->email_object->settings->email_sending_method == 'default') {
|
||||
|
||||
try {
|
||||
|
||||
$address_object = reset($this->email_object->to);
|
||||
$email = $address_object->address ?? '';
|
||||
$domain = explode("@", $email)[1] ?? "";
|
||||
$dns = dns_get_record($domain, DNS_MX);
|
||||
$server = $dns[0]["target"];
|
||||
if(stripos($server, "outlook.com") !== false) {
|
||||
|
||||
|
||||
$this->mailer = 'postmark';
|
||||
$this->client_postmark_secret = config('services.postmark-outlook.token');
|
||||
$this->mailable
|
||||
->from('maildelivery@invoice.services', 'Invoice Ninja');
|
||||
|
||||
return $this;
|
||||
}
|
||||
} catch(\Exception $e) {
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
switch ($this->email_object->settings->email_sending_method) {
|
||||
|
@ -384,7 +384,7 @@ class HtmlEngine
|
||||
|
||||
$data['$total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.total')];
|
||||
$data['$amount'] = &$data['$total'];
|
||||
$data['$amount_due'] = ['value' => &$data['$total']['value'], 'label' => ctrans('texts.amount_due')];
|
||||
$data['$amount_due'] = ['value' => &$data['$balance_due']['value'], 'label' => ctrans('texts.amount_due')];
|
||||
$data['$quote.total'] = &$data['$total'];
|
||||
$data['$invoice.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.invoice_total')];
|
||||
$data['$invoice_total_raw'] = ['value' => $this->entity_calc->getTotal(), 'label' => ctrans('texts.invoice_total')];
|
||||
|
@ -112,6 +112,7 @@
|
||||
"fakerphp/faker": "^1.14",
|
||||
"filp/whoops": "^2.7",
|
||||
"friendsofphp/php-cs-fixer": "^3.14",
|
||||
"hyvor/php-json-exporter": "^0.0.3",
|
||||
"laracasts/cypress": "^3.0",
|
||||
"larastan/larastan": "^2",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
@ -119,7 +120,6 @@
|
||||
"phpstan/phpstan": "^1.9",
|
||||
"phpunit/phpunit": "^10.0",
|
||||
"spatie/laravel-ignition": "^2.0",
|
||||
"spatie/laravel-open-telemetry": "^0.0.9",
|
||||
"spaze/phpstan-stripe": "^3.0"
|
||||
},
|
||||
"autoload": {
|
||||
|
708
composer.lock
generated
708
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -34,6 +34,10 @@ return [
|
||||
'token' => env('POSTMARK_SECRET', ''),
|
||||
],
|
||||
|
||||
'postmark-outlook' => [
|
||||
'token' => env('POSTMARK_OUTLOOK_SECRET','')
|
||||
],
|
||||
|
||||
'microsoft' => [
|
||||
'client_id' => env('MICROSOFT_CLIENT_ID'),
|
||||
'client_secret' => env('MICROSOFT_CLIENT_SECRET'),
|
||||
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Currency;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if($c = Currency::find(79)) {
|
||||
$c->thousand_separator = ',';
|
||||
$c->decimal_separator = '.';
|
||||
$c->save();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
@ -101,7 +101,7 @@ class CurrenciesSeeder extends Seeder
|
||||
['id' => 76, 'name' => 'Surinamese Dollar', 'code' => 'SRD', 'symbol' => 'SRD', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
|
||||
['id' => 77, 'name' => 'Bahraini Dinar', 'code' => 'BHD', 'symbol' => 'BD ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 78, 'name' => 'Venezuelan Bolivars', 'code' => 'VES', 'symbol' => 'Bs.', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
|
||||
['id' => 79, 'name' => 'South Korean Won', 'code' => 'KRW', 'symbol' => 'W ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
|
||||
['id' => 79, 'name' => 'South Korean Won', 'code' => 'KRW', 'symbol' => 'W ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 80, 'name' => 'Moroccan Dirham', 'code' => 'MAD', 'symbol' => 'MAD ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 81, 'name' => 'Jamaican Dollar', 'code' => 'JMD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 82, 'name' => 'Angolan Kwanza', 'code' => 'AOA', 'symbol' => 'Kz', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
|
||||
|
@ -1,4 +1,4 @@
|
||||
@component('email.template.admin', ['design' => 'light', 'settings' => $settings, 'logo' => $logo])
|
||||
@component('email.template.admin', ['design' => 'light', 'settings' => $settings, 'logo' => $logo, 'url' => $url])
|
||||
<div class="center">
|
||||
@isset($greeting)
|
||||
<p>{{ $greeting }}</p>
|
||||
|
@ -1,6 +1,7 @@
|
||||
@php
|
||||
$primary_color = isset($settings) ? $settings->primary_color : '#4caf50';
|
||||
$email_alignment = isset($settings) && $settings?->email_alignment ? $settings->email_alignment : 'center';
|
||||
$email_preferences = isset($url) && str_contains($url ?? '', '/#/') ? config('ninja.react_url').'/#/settings/user_details/notifications' : config('ninja.app_url');
|
||||
@endphp
|
||||
|
||||
<!DOCTYPE html
|
||||
@ -249,6 +250,13 @@
|
||||
<p style="text-align: center; color: #ffffff; font-size: 10px;
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif;">© {{ date('Y') }} Invoice Ninja, All Rights Reserved
|
||||
</p>
|
||||
|
||||
<a href="{{ $email_preferences }}">
|
||||
<p style="text-align: center; color: #ffffff; font-size: 10px; font-family: Verdana, Geneva, Tahoma, sans-serif;">
|
||||
{{ ctrans('texts.email_preferences') }}
|
||||
</p>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -45,6 +45,6 @@
|
||||
@endif
|
||||
@endisset
|
||||
|
||||
@isset($email_preferences)
|
||||
<p><a href="{!! $email_preferences !!}">{{ ctrans('texts.unsubscribe') }}</a></p>
|
||||
@endisset
|
||||
@if(isset($email_preferences) && $email_preferences)
|
||||
<p><a href="{!! $email_preferences !!}">{{ ctrans('texts.email_preferences') }}</a></p>
|
||||
@endif
|
@ -389,11 +389,11 @@
|
||||
</tr>
|
||||
<tr class="header-payment-due-label">
|
||||
<td>$payment_due_label:</td>
|
||||
<td>$payment_due</td>
|
||||
<td>$due_date</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="header-amount-due-label">$amount_due_label:</td>
|
||||
<td class="header-amount-due-value">$amount_due</td>
|
||||
<td class="header-amount-due-value">$balance_due</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -64,7 +64,7 @@ class TaskImportTest extends TestCase
|
||||
2 => 'client.name',
|
||||
4 => 'task.number',
|
||||
5 => 'task.description',
|
||||
6 => 'task.is_billable',
|
||||
6 => 'task.billable',
|
||||
7 => 'task.start_date',
|
||||
9 => 'task.end_date',
|
||||
8 => 'task.start_time',
|
||||
@ -132,7 +132,7 @@ class TaskImportTest extends TestCase
|
||||
3 => 'project.name',
|
||||
2 => 'client.name',
|
||||
5 => 'task.description',
|
||||
6 => 'task.is_billable',
|
||||
6 => 'task.billable',
|
||||
7 => 'task.start_date',
|
||||
9 => 'task.end_date',
|
||||
8 => 'task.start_time',
|
||||
|
Loading…
x
Reference in New Issue
Block a user