1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 21:22:58 +01:00
invoiceninja/app/Jobs/Company/CompanyImport.php

748 lines
20 KiB
PHP
Raw Normal View History

2021-05-14 07:23:00 +02:00
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Jobs\Company;
2021-05-27 07:57:07 +02:00
use App\Exceptions\ImportCompanyFailed;
2021-05-14 07:23:00 +02:00
use App\Exceptions\NonExistingMigrationFile;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Jobs\Util\UnlinkFile;
use App\Libraries\MultiDB;
use App\Mail\DownloadBackup;
use App\Mail\DownloadInvoices;
2021-05-31 01:47:14 +02:00
use App\Models\Activity;
use App\Models\Client;
use App\Models\ClientContact;
2021-05-14 07:23:00 +02:00
use App\Models\Company;
2021-05-31 01:47:14 +02:00
use App\Models\CompanyGateway;
use App\Models\CompanyLedger;
2021-05-27 07:57:07 +02:00
use App\Models\CompanyUser;
2021-05-31 01:47:14 +02:00
use App\Models\Credit;
2021-05-14 07:23:00 +02:00
use App\Models\CreditInvitation;
2021-05-31 01:47:14 +02:00
use App\Models\Document;
use App\Models\Expense;
use App\Models\ExpenseCategory;
2021-05-14 07:23:00 +02:00
use App\Models\InvoiceInvitation;
2021-05-31 01:47:14 +02:00
use App\Models\Payment;
use App\Models\PaymentTerm;
use App\Models\Paymentable;
use App\Models\Product;
use App\Models\Quote;
2021-05-14 07:23:00 +02:00
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoice;
use App\Models\RecurringInvoiceInvitation;
2021-05-31 01:47:14 +02:00
use App\Models\Subscription;
use App\Models\TaxRate;
2021-05-14 07:23:00 +02:00
use App\Models\User;
use App\Models\VendorContact;
2021-05-31 00:55:27 +02:00
use App\Utils\Ninja;
2021-05-14 07:23:00 +02:00
use App\Utils\Traits\MakesHash;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
2021-05-15 06:29:19 +02:00
use Illuminate\Support\Str;
2021-05-14 07:23:00 +02:00
use ZipArchive;
use ZipStream\Option\Archive;
use ZipStream\ZipStream;
class CompanyImport implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
2021-05-15 06:29:19 +02:00
protected $current_app_version;
private $account;
2021-05-31 00:55:27 +02:00
public $company;
public $user;
2021-05-14 07:23:00 +02:00
2021-05-31 00:55:27 +02:00
private $hash;
2021-05-14 07:23:00 +02:00
2021-05-31 00:55:27 +02:00
public $backup_file;
2021-05-14 07:23:00 +02:00
2021-05-15 06:29:19 +02:00
public $ids = [];
2021-05-31 00:55:27 +02:00
private $request_array = [];
2021-05-15 06:29:19 +02:00
private $importables = [
2021-05-31 01:47:14 +02:00
// 'company',
2021-05-15 06:29:19 +02:00
'users',
2021-05-31 01:47:14 +02:00
'company_users',
2021-05-31 00:55:27 +02:00
'payment_terms',
'tax_rates',
'expense_categories',
'task_statuses',
'clients',
'client_contacts',
'products',
'vendors',
'projects',
'company_gateways',
'client_gateway_tokens',
'group_settings',
'credits',
'invoices',
'recurring_invoices',
'quotes',
'payments',
'subscriptions',
'expenses',
'tasks',
'documents',
'webhooks',
'activities',
'backups',
'system_logs',
'company_ledger',
2021-05-15 06:29:19 +02:00
];
2021-05-14 07:23:00 +02:00
/**
* Create a new job instance.
*
* @param Company $company
* @param User $user
2021-05-31 00:55:27 +02:00
* @param string $hash - the cache hash of the import data.
* @param array $request->all()
2021-05-14 07:23:00 +02:00
*/
2021-05-31 00:55:27 +02:00
public function __construct(Company $company, User $user, string $hash, array $request_array)
2021-05-14 07:23:00 +02:00
{
$this->company = $company;
2021-05-31 00:55:27 +02:00
$this->hash = $hash;
$this->request_array = $request_array;
2021-05-15 06:29:19 +02:00
$this->current_app_version = config('ninja.app_version');
2021-05-14 07:23:00 +02:00
}
public function handle()
{
MultiDB::setDb($this->company->db);
2021-05-27 07:57:07 +02:00
$this->company = Company::where('company_key', $this->company->company_key)->firstOrFail();
2021-05-15 06:29:19 +02:00
$this->account = $this->company->account;
2021-05-14 07:23:00 +02:00
2021-05-31 00:55:27 +02:00
$this->backup_file = Cache::get($this->hash);
if ( empty( $this->import_object ) )
throw new \Exception('No import data found, has the cache expired?');
$this->backup_file = base64_decode($this->backup_file);
2021-05-14 07:23:00 +02:00
}
//check if this is a complete company import OR if it is selective
/*
Company and settings only
Data
*/
private function preFlightChecks()
{
//check the file version and perform any necessary adjustments to the file in order to proceed - needed when we change schema
2021-05-15 06:29:19 +02:00
if($this->current_app_version != $this->backup_file->app_version)
{
//perform some magic here
}
2021-05-14 07:23:00 +02:00
return $this;
}
2021-05-31 00:55:27 +02:00
private function importSettings()
2021-05-14 07:23:00 +02:00
{
2021-05-31 00:55:27 +02:00
$this->company->settings = $this->backup_file->company->settings;
$this->company->save();
2021-05-14 07:23:00 +02:00
2021-05-31 00:55:27 +02:00
return $this;
2021-05-14 07:23:00 +02:00
}
2021-05-31 01:47:14 +02:00
private function purgeCompanyData()
{
$this->company->clients()->forceDelete();
$this->company->products()->forceDelete();
$this->company->projects()->forceDelete();
$this->company->tasks()->forceDelete();
$this->company->vendors()->forceDelete();
$this->company->expenses()->forceDelete();
$this->company->save();
return $this;
}
2021-05-14 07:23:00 +02:00
private function importCompany()
{
2021-05-31 00:55:27 +02:00
$tmp_company = $this->backup_file->company;
$tmp_company->company_key = $this->createHash();
$tmp_company->db = config('database.default');
$tmp_company->account_id = $this->account->id;
if(Ninja::isHosted())
$tmp_company->subdomain = MultiDB::randomSubdomainGenerator();
else
$tmp_company->subdomain = '';
2021-05-31 01:47:14 +02:00
2021-05-31 00:55:27 +02:00
$this->company = $tmp_company;
$this->company->save();
2021-05-14 07:23:00 +02:00
return $this;
}
2021-05-31 01:47:14 +02:00
private function importData()
{
foreach($this->importables as $import){
$method = "import_{$import}";
$this->{$method}();
}
}
private function import_payment_terms()
{
$this->genericImport(PaymentTerm::class,
['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id'],
[['users' => 'user_id']],
'payment_terms',
'num_days');
return $this;
}
/* Cannot use generic as we are matching on two columns for existing data */
private function import_tax_rates()
{
foreach($this->backup_file->tax_rates as $obj)
{
$user_id = $this->transformId('users', $obj->user_id);
$obj_array = (array)$obj;
unset($obj_array['user_id']);
unset($obj_array['company_id']);
unset($obj_array['hashed_id']);
unset($obj_array['id']);
unset($obj_array['tax_rate_id']);
$new_obj = TaxRate::firstOrNew(
['name' => $obj->name, 'company_id' => $this->company->id, 'rate' => $obj->rate],
$obj_array,
);
$new_obj->save(['timestamps' => false]);
}
return $this;
}
private function import_expense_categories()
{
$this->genericImport(ExpenseCategory::class,
['user_id', 'company_id', 'id', 'hashed_id'],
[['users' => 'user_id']],
'expense_categories',
'name');
return $this;
}
private function import_task_statuses()
{
}
private function import_clients()
{
}
private function import_client_contacts()
{
}
private function import_products()
{
}
private function import_vendors()
{
}
private function import_projects()
{
}
private function import_company_gateways()
{
}
private function import_client_gateway_tokens()
{
}
private function import_group_settings()
{
}
private function import_credits()
{
}
private function import_invoices()
{
}
private function import_recurring_invoices()
{
}
private function import_quotes()
{
}
private function import_quotes()
{
}
private function import_payments()
{
}
private function import_subscriptions()
{
}
private function import_expenses()
{
}
private function import_tasks()
{
}
private function import_documents()
{
}
private function import_webhooks()
{
}
private function import_activities()
{
}
private function import_backups()
{
}
private function import_system_logs()
{
}
private function import_company_ledger()
{
}
private function import_users()
2021-05-14 07:23:00 +02:00
{
2021-05-15 06:29:19 +02:00
User::unguard();
2021-05-14 07:23:00 +02:00
2021-05-15 06:29:19 +02:00
foreach ($this->backup_file->users as $user)
{
2021-05-27 07:57:07 +02:00
if(User::where('email', $user->email)->where('account_id', '!=', $this->account->id)->exists())
throw new ImportCompanyFailed("{$user->email} is already in the system attached to a different account");
2021-05-15 06:29:19 +02:00
$new_user = User::firstOrNew(
['email' => $user->email],
(array)$user,
);
$new_user->account_id = $this->account->id;
$new_user->save(['timestamps' => false]);
2021-05-27 07:57:07 +02:00
$this->ids['users']["{$user->hashed_id}"] = $new_user->id;
}
User::reguard();
}
2021-05-31 01:47:14 +02:00
private function import_company_users()
2021-05-27 07:57:07 +02:00
{
CompanyUser::unguard();
foreach($this->backup_file->company_users as $cu)
{
$user_id = $this->transformId($cu->user_id);
$new_cu = CompanyUser::firstOrNew(
['user_id' => $user_id, 'company_id', $this->company->id],
(array)$cu,
);
$new_cu->account_id = $this->account->id;
$new_cu->save(['timestamps' => false]);
2021-05-15 06:29:19 +02:00
}
2021-05-27 07:57:07 +02:00
CompanyUser::reguard();
2021-05-15 06:29:19 +02:00
}
2021-05-31 01:47:14 +02:00
private function documentsImport()
{
foreach($this->backup_json_object->documents as $document)
{
$new_document = new Document();
$new_document->user_id = $this->transformId('users', $document->user_id);
$new_document->assigned_user_id = $this->transformId('users', $document->assigned_user_id);
$new_document->company_id = $this->company->id;
$new_document->project_id = $this->transformId('projects', $document->project_id);
$new_document->vendor_id = $this->transformId('vendors', $document->vendor_id);
$new_document->url = $document->url;
$new_document->preview = $document->preview;
$new_document->name = $document->name;
$new_document->type = $document->type;
$new_document->disk = $document->disk;
$new_document->hash = $document->hash;
$new_document->size = $document->size;
$new_document->width = $document->width;
$new_document->height = $document->height;
$new_document->is_default = $document->is_default;
$new_document->custom_value1 = $document->custom_value1;
$new_document->custom_value2 = $document->custom_value2;
$new_document->custom_value3 = $document->custom_value3;
$new_document->custom_value4 = $document->custom_value4;
$new_document->deleted_at = $document->deleted_at;
$new_document->documentable_id = $this->transformDocumentId($document->documentable_id, $document->documentable_type);
$new_document->documentable_type = $document->documentable_type;
$new_document->save(['timestamps' => false]);
}
}
private function transformDocumentId($id, $type)
{
switch ($type) {
case Company::class:
return $this->company->id;
break;
case Client::class:
return $this->transformId('clients', $id);
break;
case ClientContact::class:
return $this->transformId('client_contacts', $id);
break;
case Credit::class:
return $this->transformId('credits', $id);
break;
case Expense::class:
return $this->transformId('expenses', $id);
break;
case 'invoices':
return $this->transformId('invoices', $id);
break;
case Payment::class:
return $this->transformId('payments', $id);
break;
case Product::class:
return $this->transformId('products', $id);
break;
case Quote::class:
return $this->transformId('quotes', $id);
break;
case RecurringInvoice::class:
return $this->transformId('recurring_invoices', $id);
break;
case Company::class:
return $this->transformId('clients', $id);
break;
default:
# code...
break;
}
}
private function paymentablesImport()
{
foreach($this->backup_json_object->payments as $payment)
{
foreach($payment->paymentables as $paymentable_obj)
{
$paymentable = new Paymentable();
$paymentable->payment_id = $this->transformId('payments', $paymentable_obj->payment_id);
$paymentable->paymentable_type = $paymentable_obj->paymentable_type;
$paymentable->amount = $paymentable_obj->amount;
$paymentable->refunded = $paymentable_obj->refunded;
$paymentable->created_at = $paymentable_obj->created_at;
$paymentable->deleted_at = $paymentable_obj->deleted_at;
$paymentable->updated_at = $paymentable_obj->updated_at;
$paymentable->paymentable_id = $this->convertPaymentableId($paymentable_obj->paymentable_type, $paymentable_obj->paymentable_id);
$paymentable->paymentable_type = $paymentable_obj->paymentable_type;
$paymentable->save(['timestamps' => false]);
}
}
}
private function convertPaymentableId($type, $id)
{
switch ($type) {
case 'invoices':
return $this->transformId('invoices', $id);
break;
case Credit::class:
return $this->transformId('credits', $id);
break;
case Payment::class:
return $this->transformId('payments', $id);
default:
# code...
break;
}
}
private function genericNewClassImport($class, $unset, $transforms, $object_property)
{
$class::unguard();
foreach($this->backup_json_object->{$object_property} as $obj)
{
/* Remove unwanted keys*/
$obj_array = (array)$obj;
foreach($unset as $un){
unset($obj_array[$un]);
}
$activity_invitation_key = false;
if($class instanceof Activity){
if(isset($obj->invitation_id)){
if(isset($obj->invoice_id))
$activity_invitation_key = 'invoice_invitations';
elseif(isset($obj->quote_id))
$activity_invitation_key = 'quote_invitations';
elseif($isset($obj->credit_id))
$activity_invitation_key = 'credit_invitations';
}
}
/* Transform old keys to new keys */
foreach($transforms as $transform)
{
foreach($transform as $key => $value)
{
if($class instanceof Activity && $activity_invitation_key)
$key = $activity_invitation_key;
$obj_array["{$value}"] = $this->transformId($key, $obj->{$value});
}
}
if($class instanceof CompanyGateway) {
$obj_array['config'] = encrypt($obj_array['config']);
}
$new_obj = new $class();
$new_obj->company_id = $this->company->id;
$new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]);
$this->ids["{$object_property}"]["{$obj->hashed_id}"] = $new_obj->id;
}
$class::reguard();
}
2021-05-15 06:29:19 +02:00
2021-05-31 01:47:14 +02:00
private function genericImportWithoutCompany($class, $unset, $transforms, $object_property, $match_key)
2021-05-15 06:29:19 +02:00
{
2021-05-31 01:47:14 +02:00
$class::unguard();
foreach($this->backup_json_object->{$object_property} as $obj)
{
/* Remove unwanted keys*/
$obj_array = (array)$obj;
foreach($unset as $un){
unset($obj_array[$un]);
}
/* Transform old keys to new keys */
foreach($transforms as $transform)
{
foreach($transform as $key => $value)
{
$obj_array["{$value}"] = $this->transformId($key, $obj->{$value});
}
}
/* New to convert product ids from old hashes to new hashes*/
if($class == 'App\Models\Subscription'){
$obj_array['product_ids'] = $this->recordProductIds($obj_array['product_ids']);
$obj_array['recurring_product_ids'] = $this->recordProductIds($obj_array['recurring_product_ids']);
}
$new_obj = $class::firstOrNew(
[$match_key => $obj->{$match_key}],
$obj_array,
);
$new_obj->save(['timestamps' => false]);
if($new_obj instanceof CompanyLedger){
}
else
$this->ids["{$object_property}"]["{$obj->hashed_id}"] = $new_obj->id;
}
$class::reguard();
}
private function genericImport($class, $unset, $transforms, $object_property, $match_key)
{
$class::unguard();
foreach($this->backup_json_object->{$object_property} as $obj)
{
/* Remove unwanted keys*/
$obj_array = (array)$obj;
foreach($unset as $un){
unset($obj_array[$un]);
}
/* Transform old keys to new keys */
foreach($transforms as $transform)
{
foreach($transform as $key => $value)
{
$obj_array["{$value}"] = $this->transformId($key, $obj->{$value});
}
}
/* New to convert product ids from old hashes to new hashes*/
if($class == 'App\Models\Subscription'){
$obj_array['product_ids'] = $this->recordProductIds($obj_array['product_ids']);
$obj_array['recurring_product_ids'] = $this->recordProductIds($obj_array['recurring_product_ids']);
}
$new_obj = $class::firstOrNew(
[$match_key => $obj->{$match_key}, 'company_id' => $this->company->id],
$obj_array,
);
$new_obj->save(['timestamps' => false]);
if($new_obj instanceof CompanyLedger){
}
else
$this->ids["{$object_property}"]["{$obj->hashed_id}"] = $new_obj->id;
}
$class::reguard();
}
private function recordProductIds($ids)
{
$id_array = explode(",", $ids);
$tmp_arr = [];
foreach($id_array as $id) {
$tmp_arr[] = $this->encodePrimaryKey($this->transformId('products', $id));
}
return implode(",", $tmp_arr);
}
private function transformId(string $resource, ?string $old): ?int
{
if(empty($old))
return null;
2021-05-15 06:29:19 +02:00
if (! array_key_exists($resource, $this->ids)) {
throw new \Exception("Resource {$resource} not available.");
}
if (! array_key_exists("{$old}", $this->ids[$resource])) {
2021-05-31 01:47:14 +02:00
throw new \Exception("Missing {$resource} key: {$old}");
2021-05-15 06:29:19 +02:00
}
return $this->ids[$resource]["{$old}"];
2021-05-14 07:23:00 +02:00
}
2021-05-31 01:47:14 +02:00
2021-05-14 07:23:00 +02:00
}