diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 607e638b55eb..f7d4240719fb 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -202,7 +202,7 @@ class CompanySettings extends BaseSettings public $schedule_reminder2 = ''; // (enum: after_invoice_date, before_due_date, after_due_date) implmemented public $schedule_reminder3 = ''; // (enum: after_invoice_date, before_due_date, after_due_date) implmemented - public $reminder_send_time = 32400; //number of seconds from UTC +0 to send reminders @TODO + public $reminder_send_time = 0; //number of seconds from UTC +0 to send reminders @TODO public $late_fee_amount1 = 0; //@implemented public $late_fee_amount2 = 0; //@implemented diff --git a/app/Exceptions/ImportCompanyFailed.php b/app/Exceptions/ImportCompanyFailed.php new file mode 100644 index 000000000000..9bb887ccd647 --- /dev/null +++ b/app/Exceptions/ImportCompanyFailed.php @@ -0,0 +1,10 @@ +user(), $company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); $user->setCompany($company); diff --git a/app/Jobs/Company/CompanyExport.php b/app/Jobs/Company/CompanyExport.php index fbf9a88df68b..0467eeb67025 100644 --- a/app/Jobs/Company/CompanyExport.php +++ b/app/Jobs/Company/CompanyExport.php @@ -101,7 +101,7 @@ class CompanyExport implements ShouldQueue return $activity; - })->makeHidden(['id'])->toArray(); + })->makeHidden(['id'])->all(); $this->export_data['backups'] = $this->company->all_activities()->with('backup')->cursor()->map(function ($activity){ @@ -114,16 +114,16 @@ class CompanyExport implements ShouldQueue return $backup; - })->toArray(); + })->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); + // $user->id = $this->encodePrimaryKey($user->id); return $user; - })->toArray(); + })->all(); $this->export_data['client_contacts'] = $this->company->client_contacts->map(function ($client_contact){ @@ -132,7 +132,7 @@ class CompanyExport implements ShouldQueue return $client_contact; - })->toArray(); + })->all(); $this->export_data['client_gateway_tokens'] = $this->company->client_gateway_tokens->map(function ($client_gateway_token){ @@ -141,16 +141,17 @@ class CompanyExport implements ShouldQueue return $client_gateway_token; - })->toArray(); + })->all(); - $this->export_data['clients'] = $this->company->clients->map(function ($client){ + $this->export_data['clients'] = $this->company->clients->makeVisible(['id','private_notes','user_id','company_id','last_login'])->map(function ($client){ - $client = $this->transformArrayOfKeys($client, ['id', 'company_id', 'user_id',' assigned_user_id', 'group_settings_id']); + $client = $this->transformArrayOfKeys($client, ['company_id', 'user_id', 'assigned_user_id', 'group_settings_id']); - return $client; + return $client->makeVisible(['id','private_notes','user_id','company_id','last_login']); + + })->all(); - })->toArray(); $this->export_data['company'] = $this->company->toArray(); @@ -161,7 +162,7 @@ class CompanyExport implements ShouldQueue return $company_gateway; - })->toArray(); + })->all(); $this->export_data['company_tokens'] = $this->company->tokens->map(function ($token){ @@ -169,7 +170,7 @@ class CompanyExport implements ShouldQueue return $token; - })->toArray(); + })->all(); $this->export_data['company_ledger'] = $this->company->ledger->map(function ($ledger){ @@ -177,7 +178,7 @@ class CompanyExport implements ShouldQueue return $ledger; - })->toArray(); + })->all(); $this->export_data['company_users'] = $this->company->company_users->map(function ($company_user){ @@ -185,7 +186,7 @@ class CompanyExport implements ShouldQueue return $company_user; - })->toArray(); + })->all(); $this->export_data['credits'] = $this->company->credits->map(function ($credit){ @@ -194,7 +195,7 @@ class CompanyExport implements ShouldQueue return $credit; - })->toArray(); + })->all(); $this->export_data['credit_invitations'] = CreditInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($credit){ @@ -203,9 +204,9 @@ class CompanyExport implements ShouldQueue return $credit; - })->toArray(); + })->all(); - $this->export_data['designs'] = $this->company->user_designs->makeHidden(['id'])->toArray(); + $this->export_data['designs'] = $this->company->user_designs->makeHidden(['id'])->all(); $this->export_data['documents'] = $this->company->documents->map(function ($document){ @@ -213,15 +214,15 @@ class CompanyExport implements ShouldQueue return $document; - })->toArray(); + })->all(); - $this->export_data['expense_categories'] = $this->company->expenses->map(function ($expense_category){ + $this->export_data['expense_categories'] = $this->company->expense_categories->map(function ($expense_category){ $expense_category = $this->transformArrayOfKeys($expense_category, ['user_id', 'company_id']); return $expense_category; - })->toArray(); + })->all(); $this->export_data['expenses'] = $this->company->expenses->map(function ($expense){ @@ -231,7 +232,7 @@ class CompanyExport implements ShouldQueue return $expense; - })->toArray(); + })->all(); $this->export_data['group_settings'] = $this->company->group_settings->map(function ($gs){ @@ -239,7 +240,7 @@ class CompanyExport implements ShouldQueue return $gs; - })->toArray(); + })->all(); $this->export_data['invoices'] = $this->company->invoices->map(function ($invoice){ @@ -249,7 +250,7 @@ class CompanyExport implements ShouldQueue return $invoice; - })->toArray(); + })->all(); $this->export_data['invoice_invitations'] = InvoiceInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($invoice){ @@ -258,7 +259,7 @@ class CompanyExport implements ShouldQueue return $invoice; - })->toArray(); + })->all(); $this->export_data['payment_terms'] = $this->company->user_payment_terms->map(function ($term){ @@ -266,7 +267,7 @@ class CompanyExport implements ShouldQueue return $term; - })->makeHidden(['id'])->toArray(); + })->makeHidden(['id'])->all(); $this->export_data['paymentables'] = $this->company->payments()->with('paymentables')->cursor()->map(function ($paymentable){ @@ -274,7 +275,7 @@ class CompanyExport implements ShouldQueue return $paymentable; - })->toArray(); + })->all(); $this->export_data['payments'] = $this->company->payments->map(function ($payment){ @@ -283,7 +284,7 @@ class CompanyExport implements ShouldQueue return $payment; - })->toArray(); + })->all(); $this->export_data['projects'] = $this->company->projects->map(function ($project){ @@ -293,7 +294,7 @@ class CompanyExport implements ShouldQueue return $project; - })->toArray(); + })->all(); $this->export_data['quotes'] = $this->company->quotes->map(function ($quote){ @@ -302,7 +303,7 @@ class CompanyExport implements ShouldQueue return $quote; - })->toArray(); + })->all(); $this->export_data['quote_invitations'] = QuoteInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($quote){ @@ -311,7 +312,7 @@ class CompanyExport implements ShouldQueue return $quote; - })->toArray(); + })->all(); $this->export_data['recurring_invoices'] = $this->company->recurring_invoices->map(function ($ri){ @@ -320,7 +321,7 @@ class CompanyExport implements ShouldQueue $ri = $this->transformArrayOfKeys($ri, ['client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']); return $ri; - })->toArray(); + })->all(); $this->export_data['recurring_invoice_invitations'] = RecurringInvoiceInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($ri){ @@ -329,7 +330,7 @@ class CompanyExport implements ShouldQueue return $ri; - })->toArray(); + })->all(); $this->export_data['subscriptions'] = $this->company->subscriptions->map(function ($subscription){ @@ -338,7 +339,7 @@ class CompanyExport implements ShouldQueue return $subscription; - })->toArray(); + })->all(); $this->export_data['system_logs'] = $this->company->system_logs->map(function ($log){ @@ -348,7 +349,7 @@ class CompanyExport implements ShouldQueue return $log; - })->makeHidden(['id'])->toArray(); + })->makeHidden(['id'])->all(); $this->export_data['tasks'] = $this->company->tasks->map(function ($task){ @@ -357,7 +358,7 @@ class CompanyExport implements ShouldQueue return $task; - })->toArray(); + })->all(); $this->export_data['task_statuses'] = $this->company->task_statuses->map(function ($status){ @@ -367,7 +368,7 @@ class CompanyExport implements ShouldQueue return $status; - })->toArray(); + })->all(); $this->export_data['tax_rates'] = $this->company->tax_rates->map(function ($rate){ @@ -376,13 +377,13 @@ class CompanyExport implements ShouldQueue return $rate; - })->makeHidden(['id'])->toArray(); + })->makeHidden(['id'])->all(); $this->export_data['vendors'] = $this->company->vendors->map(function ($vendor){ return $this->transformBasicEntities($vendor); - })->toArray(); + })->all(); $this->export_data['vendor_contacts'] = VendorContact::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($vendor){ @@ -392,7 +393,7 @@ class CompanyExport implements ShouldQueue return $vendor; - })->toArray(); + })->all(); $this->export_data['webhooks'] = $this->company->webhooks->map(function ($hook){ @@ -401,7 +402,7 @@ class CompanyExport implements ShouldQueue return $hook; - })->makeHidden(['id'])->toArray(); + })->makeHidden(['id'])->all(); //write to tmp and email to owner(); diff --git a/app/Jobs/Company/CompanyImport.php b/app/Jobs/Company/CompanyImport.php index 55177cd45579..928954cd0f40 100644 --- a/app/Jobs/Company/CompanyImport.php +++ b/app/Jobs/Company/CompanyImport.php @@ -11,6 +11,7 @@ namespace App\Jobs\Company; +use App\Exceptions\ImportCompanyFailed; use App\Exceptions\NonExistingMigrationFile; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; @@ -19,6 +20,7 @@ use App\Libraries\MultiDB; use App\Mail\DownloadBackup; use App\Mail\DownloadInvoices; use App\Models\Company; +use App\Models\CompanyUser; use App\Models\CreditInvitation; use App\Models\InvoiceInvitation; use App\Models\QuoteInvitation; @@ -63,6 +65,8 @@ class CompanyImport implements ShouldQueue 'users', // 'payment_terms', // 'tax_rates', + // 'expense_categories', + // 'task_statuses', // 'clients', // 'company_gateways', // 'client_gateway_tokens', @@ -74,11 +78,15 @@ class CompanyImport implements ShouldQueue // 'recurring_invoices', // 'quotes', // 'payments', - // 'expense_categories', - // 'task_statuses', // 'expenses', // 'tasks', // 'documents', + // 'subscriptions', + // 'webhooks', + // 'system_logs', + // 'paymentables', + // 'company_ledger', + // 'backups', ]; /** @@ -100,7 +108,7 @@ class CompanyImport implements ShouldQueue { MultiDB::setDb($this->company->db); - $this->company =Company::where('company_key', $this->company->company_key)->firstOrFail(); + $this->company = Company::where('company_key', $this->company->company_key)->firstOrFail(); $this->account = $this->company->account; $this->unzipFile() @@ -168,6 +176,9 @@ class CompanyImport implements ShouldQueue foreach ($this->backup_file->users as $user) { + 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"); + $new_user = User::firstOrNew( ['email' => $user->email], (array)$user, @@ -176,15 +187,38 @@ class CompanyImport implements ShouldQueue $new_user->account_id = $this->account->id; $new_user->save(['timestamps' => false]); - $this->ids['users']["{$user->id}"] = $new_user->id; + $this->ids['users']["{$user->hashed_id}"] = $new_user->id; + } - Expense::reguard(); + User::reguard(); + + } + + private function importCompanyUsers() + { + 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]); + + } + + CompanyUser::reguard(); } - public function transformId(string$resource, string $old): int + public function transformId(string $resource, string $old): int { if (! array_key_exists($resource, $this->ids)) { throw new \Exception("Resource {$resource} not available."); diff --git a/tests/Feature/Import/ImportCompanyTest.php b/tests/Feature/Import/ImportCompanyTest.php index a30fe6f1144e..50853ebb4be1 100644 --- a/tests/Feature/Import/ImportCompanyTest.php +++ b/tests/Feature/Import/ImportCompanyTest.php @@ -13,10 +13,17 @@ namespace Tests\Feature\Import; use App\Jobs\Import\CSVImport; use App\Models\Account; use App\Models\Client; +use App\Models\Company; +use App\Models\CompanyToken; +use App\Models\CompanyUser; use App\Models\Expense; +use App\Models\ExpenseCategory; use App\Models\Invoice; use App\Models\Payment; +use App\Models\PaymentTerm; use App\Models\Product; +use App\Models\TaskStatus; +use App\Models\TaxRate; use App\Models\User; use App\Models\Vendor; use App\Utils\Traits\MakesHash; @@ -37,6 +44,9 @@ class ImportCompanyTest extends TestCase use MakesHash; public $account; + public $company; + public $backup_json_object; + public $ids; public function setUp() :void { @@ -46,10 +56,28 @@ class ImportCompanyTest extends TestCase ThrottleRequests::class ); - $this->withoutExceptionHandling(); + Account::all()->each(function ($account){ + $account->delete(); + }); + $this->account = Account::factory()->create(); + $this->company = Company::factory()->create(['account_id' => $this->account->id]); + + $backup_json_file_zip = base_path().'/tests/Feature/Import/backup.zip'; + + $zip = new \ZipArchive; + $res = $zip->open($backup_json_file_zip); + if ($res === TRUE) { + $zip->extractTo(sys_get_temp_dir()); + $zip->close(); + } + + $backup_json_file = sys_get_temp_dir() . "/backup/backup.json"; + + $this->backup_json_object = json_decode(file_get_contents($backup_json_file)); + } public function testBackupJsonRead() @@ -71,25 +99,18 @@ class ImportCompanyTest extends TestCase unlink($backup_json_file); } + public function testAppVersion() + { + $this->assertEquals("5.1.65", $this->backup_json_object->app_version); + } + public function testImportUsers() { - $backup_json_file_zip = base_path().'/tests/Feature/Import/backup.zip'; - $zip = new \ZipArchive; - $res = $zip->open($backup_json_file_zip); - if ($res === TRUE) { - $zip->extractTo(sys_get_temp_dir()); - $zip->close(); - } + $this->assertTrue(property_exists($this->backup_json_object, 'app_version')); - $backup_json_file = sys_get_temp_dir() . "/backup/backup.json"; - - $backup_json_object = json_decode(file_get_contents($backup_json_file)); - - $this->assertTrue(property_exists($backup_json_object, 'app_version')); - $this->assertTrue(property_exists($backup_json_object, 'users')); - - unlink($backup_json_file); + /***************************** Users *****************************/ + $this->assertTrue(property_exists($this->backup_json_object, 'users')); User::all()->each(function ($user){ $user->forceDelete(); @@ -97,7 +118,9 @@ class ImportCompanyTest extends TestCase User::unguard(); - foreach ($backup_json_object->users as $user) + $this->assertEquals(2, count($this->backup_json_object->users)); + + foreach ($this->backup_json_object->users as $user) { $user_array = (array)$user; unset($user_array['laravel_through_key']); @@ -117,6 +140,264 @@ class ImportCompanyTest extends TestCase User::reguard(); $this->assertEquals(2, User::count()); + /***************************** Users *****************************/ + + + /***************************** Company Users *****************************/ + + $this->assertEquals(2, count($this->backup_json_object->company_users)); + + CompanyUser::unguard(); + + foreach($this->backup_json_object->company_users as $cu) + { + $user_id = $this->transformId('users', $cu->user_id); + + $cu_array = (array)$cu; + unset($cu_array['user_id']); + unset($cu_array['company_id']); + unset($cu_array['account_id']); + unset($cu_array['hashed_id']); + unset($cu_array['id']); + + $new_cu = CompanyUser::firstOrNew( + ['user_id' => $user_id, 'company_id' => $this->company->id], + $cu_array, + ); + + $new_cu->account_id = $this->account->id; + $new_cu->save(['timestamps' => false]); + + } + + CompanyUser::reguard(); + + $this->assertEquals(2, CompanyUser::count()); + /***************************** Company Users *****************************/ + + + /***************************** Company Tokens *****************************/ + + $this->assertEquals(2, count($this->backup_json_object->company_tokens)); + + CompanyToken::unguard(); + + foreach($this->backup_json_object->company_tokens as $ct) + { + $user_id = $this->transformId('users', $ct->user_id); + + $ct_array = (array)$ct; + unset($ct_array['user_id']); + unset($ct_array['company_id']); + unset($ct_array['account_id']); + unset($ct_array['hashed_id']); + unset($ct_array['id']); + + $new_ct = CompanyToken::firstOrNew( + ['user_id' => $user_id, 'company_id' => $this->company->id], + $ct_array, + ); + + $new_ct->account_id = $this->account->id; + $new_ct->save(['timestamps' => false]); + + } + + CompanyToken::reguard(); + + $this->assertEquals(2, CompanyToken::count()); + /***************************** Company Tokens *****************************/ + + + /***************************** Payment Terms *****************************/ + PaymentTerm::unguard(); + + $this->assertEquals(8, count($this->backup_json_object->payment_terms)); + + foreach($this->backup_json_object->payment_terms 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['account_id']); + unset($obj_array['hashed_id']); + unset($obj_array['id']); + + $new_obj = PaymentTerm::firstOrNew( + ['num_days' => $obj->num_days, 'company_id' => $this->company->id], + $obj_array, + ); + + $new_obj->save(['timestamps' => false]); + + } + + PaymentTerm::reguard(); + + $this->assertEquals(8, PaymentTerm::count()); + /***************************** Payment Terms *****************************/ + + /***************************** Tax Rates *****************************/ + TaxRate::unguard(); + + $this->assertEquals(2, count($this->backup_json_object->tax_rates)); + + foreach($this->backup_json_object->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['account_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]); + + } + + TaxRate::reguard(); + + $this->assertEquals(2, TaxRate::count()); + /***************************** Tax Rates *****************************/ + + /***************************** Expense Category *****************************/ + ExpenseCategory::unguard(); + + $this->assertEquals(2, count($this->backup_json_object->expense_categories)); + + foreach($this->backup_json_object->expense_categories 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['account_id']); + unset($obj_array['hashed_id']); + unset($obj_array['id']); + unset($obj_array['tax_rate_id']); + + $new_obj = ExpenseCategory::firstOrNew( + ['name' => $obj->name, 'company_id' => $this->company->id], + $obj_array, + ); + + $new_obj->save(['timestamps' => false]); + + } + + ExpenseCategory::reguard(); + + $this->assertEquals(2, ExpenseCategory::count()); + /***************************** Expense Category *****************************/ + + + /***************************** Task Statuses *****************************/ + TaskStatus::unguard(); + + $this->assertEquals(4, count($this->backup_json_object->task_statuses)); + + foreach($this->backup_json_object->task_statuses 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['account_id']); + unset($obj_array['hashed_id']); + unset($obj_array['id']); + unset($obj_array['tax_rate_id']); + + $new_obj = TaskStatus::firstOrNew( + ['name' => $obj->name, 'company_id' => $this->company->id], + $obj_array, + ); + + $new_obj->save(['timestamps' => false]); + + } + + TaskStatus::reguard(); + + $this->assertEquals(4, TaskStatus::count()); + /***************************** Task Statuses *****************************/ + + /***************************** Clients *****************************/ + Client::unguard(); + + $this->assertEquals(1, count($this->backup_json_object->clients)); + + foreach($this->backup_json_object->clients 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['account_id']); + unset($obj_array['hashed_id']); + unset($obj_array['id']); + unset($obj_array['gateway_tokens']); + unset($obj_array['contacts']); + unset($obj_array['documents']); + + // $obj_array['settings'] = json_encode($obj_array['settings']); + // nlog($obj_array); + + $new_obj = Client::firstOrNew( + ['number' => $obj->number, 'company_id' => $this->company->id], + $obj_array, + ); + + $new_obj->save(['timestamps' => false]); + + $this->ids['clients']["{$obj->hashed_id}"] = $new_obj->id; + + } + + Client::reguard(); + + $this->assertEquals(1, Client::count()); + /***************************** Clients *****************************/ + + + } + + + private function transformId(string $resource, string $old): int + { + if (! array_key_exists($resource, $this->ids)) { + throw new \Exception("Resource {$resource} not available."); + } + + if (! array_key_exists("{$old}", $this->ids[$resource])) { + throw new \Exception("Missing resource key: {$old}"); + } + + return $this->ids[$resource]["{$old}"]; + } + + public function tearDown() :void + { + $backup_json_file = sys_get_temp_dir() . "/backup/backup.json"; + + // unlink($backup_json_file); } diff --git a/tests/Feature/Import/backup.zip b/tests/Feature/Import/backup.zip index 306468eeaff4..302ba5a47a60 100644 Binary files a/tests/Feature/Import/backup.zip and b/tests/Feature/Import/backup.zip differ diff --git a/tests/Feature/UserTest.php b/tests/Feature/UserTest.php index ab63f8d66917..0e7b0fd0f88c 100644 --- a/tests/Feature/UserTest.php +++ b/tests/Feature/UserTest.php @@ -119,6 +119,7 @@ class UserTest extends TestCase } catch (ValidationException $e) { $message = json_decode($e->validator->getMessageBag(), 1); nlog($message); + var_dump($message); $this->assertNotNull($message); } @@ -130,7 +131,7 @@ class UserTest extends TestCase $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, - 'X-API-PASSWORD' => 'ALongAndBriliantPassword', + 'X-API-PASSWORD' => 'ALongAndBriliantPassword', ])->delete('/api/v1/users/'.$this->encodePrimaryKey($user->id).'/detach_from_company?include=company_user'); $response->assertStatus(200);