Fixes for KRW currency formatting

This commit is contained in:
David Bomba 2024-02-07 07:42:46 +11:00
parent 1459fb2790
commit f16e8fdd15
23 changed files with 559 additions and 692 deletions

View File

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

View File

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

View File

@ -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,13 +68,23 @@ 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);
$info = $this->writer->collection('');
set_time_limit(0);
$this->export_data['app_version'] = config('ninja.app_version');
$this->export_data['storage_url'] = Storage::url('');
$info->addItems($this->export_data);
$this->export_data['activities'] = $this->company->all_activities->map(function ($activity) {
$activity = $this->transformArrayOfKeys($activity, [
'user_id',
@ -98,27 +109,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 +143,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 +154,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 +165,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 +186,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 +236,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 +247,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 +268,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,12 +290,23 @@ 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']);
return $gs->makeVisible(['id']);
})->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);
@ -238,18 +321,33 @@ 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']);
return $term;
})->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);
@ -260,6 +358,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 +372,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 +385,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']);
@ -281,6 +398,11 @@ class CompanyExport implements ShouldQueue
return $quote->makeVisible(['id']);
})->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']);
@ -288,6 +410,12 @@ class CompanyExport implements ShouldQueue
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 +423,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']);
@ -302,6 +437,11 @@ class CompanyExport implements ShouldQueue
return $ri->makeVisible(['id']);
})->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']);
@ -309,6 +449,13 @@ class CompanyExport implements ShouldQueue
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 +470,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 +481,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 +509,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 +523,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 +548,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 +562,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 +580,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 +594,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 +669,40 @@ 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, '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);

View File

@ -278,7 +278,6 @@ class CompanyImport implements ShouldQueue
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;
@ -341,15 +340,37 @@ class CompanyImport implements ShouldQueue
{
$path = TempFile::filePath(Storage::disk(config('filesystems.default'))->get($this->file_location), basename($this->file_location));
nlog($path);
$zip = new ZipArchive();
$archive = $zip->open($path);
$res = $zip->open($path, ZipArchive::OVERWRITE);
$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';
$zip->extractTo('test');
$zip->close();
// $file_path = sys_get_temp_dir().'/'.sha1(microtime());
// nlog($file_path);
// $result = $zip->extractTo($file_path);
$result = $zip->extractTo(".");
nlog($result);
$$zip->close();
} else {
echo 'failed, code:' . $res;
}
$file_location = "backup.json";
$file_path = $file_location;
if (! file_exists($file_path)) {
throw new NonExistingMigrationFile('Backup file does not exist, or is corrupted.');
}

View File

@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {

View File

@ -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')];

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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'),

View File

@ -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
{
//
}
};

View File

@ -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' => ','],

View File

@ -1,30 +0,0 @@
includes:
- ./vendor/larastan/larastan/extension.neon
- ./vendor/spaze/phpstan-stripe/extension.neon
- phpstan-baseline.neon
parameters:
level: 2
paths:
- app
excludePaths:
- 'vendor/*'
- 'resources/*/*.php'
- 'app/Jobs/Ninja/*'
- 'app/Models/Presenters/*'
- 'app/Console/Commands/*'
- 'app/DataMapper/Analytics/*'
- 'app/PaymentDrivers/Authorize/*'
- 'app/PaymentDrivers/AuthorizePaymentDriver.php'
- 'app/Utils/Traits/*'
universalObjectCratesClasses:
- App\DataMapper\Tax\RuleInterface
- App\DataMapper\FeesAndLimits
- \Postmark\Models\DynamicResponseModel
- \Stripe\Collection
reportUnmatchedIgnoredErrors: false
ignoreErrors:
- '#Array has 2 duplicate keys with value#'
- '#Call to an undefined method#'
- '#makeHidden#'
- '#Socialite#'
- '#Access to protected property#'

View File

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false"
bootstrap="vendor/autoload.php" colors="true" processIsolation="true" stopOnError="true" stopOnFailure="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.3/phpunit.xsd"
cacheDirectory=".phpunit.cache" backupStaticProperties="false">
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Integration">
<directory suffix="Test.php">./tests/Integration</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
<testsuite name="Pdf">
<directory suffix="Test.php">./tests/Pdf</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_DRIVER" value="file"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="MAIL_MAILER" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="DB_STRICT" value="FALSE"/>
</php>
<logging/>
</phpunit>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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',