1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-18 00:53:10 +01:00
invoiceninja/app/Jobs/Util/Import.php

1540 lines
48 KiB
PHP
Raw Normal View History

<?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\Util;
2020-10-07 05:00:32 +02:00
use App\DataMapper\Analytics\MigrationFailure;
use App\DataMapper\CompanySettings;
use App\Exceptions\MigrationValidatorFailed;
use App\Exceptions\ProcessingMigrationArchiveFailed;
use App\Exceptions\ResourceDependencyMissing;
use App\Exceptions\ResourceNotAvailableForMigration;
use App\Factory\ClientFactory;
2020-07-01 07:51:19 +02:00
use App\Factory\CompanyLedgerFactory;
use App\Factory\CreditFactory;
use App\Factory\InvoiceFactory;
use App\Factory\PaymentFactory;
use App\Factory\ProductFactory;
use App\Factory\QuoteFactory;
2020-10-02 00:19:42 +02:00
use App\Factory\RecurringInvoiceFactory;
use App\Factory\TaxRateFactory;
use App\Factory\UserFactory;
use App\Factory\VendorFactory;
use App\Http\Requests\Company\UpdateCompanyRequest;
use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule;
use App\Http\ValidationRules\ValidUserForCompany;
use App\Jobs\Company\CreateCompanyToken;
use App\Jobs\Ninja\CheckCompanyData;
2020-07-19 09:17:19 +02:00
use App\Jobs\Ninja\CompanySizeCheck;
2021-02-14 10:55:04 +01:00
use App\Jobs\Util\VersionCheck;
use App\Libraries\MultiDB;
use App\Mail\MigrationCompleted;
2020-07-01 07:51:19 +02:00
use App\Models\Activity;
use App\Models\Client;
2021-01-19 23:35:52 +01:00
use App\Models\ClientContact;
use App\Models\ClientGatewayToken;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\Credit;
use App\Models\Document;
2020-10-28 11:10:49 +01:00
use App\Models\Expense;
use App\Models\ExpenseCategory;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentTerm;
use App\Models\Product;
2020-10-30 13:01:30 +01:00
use App\Models\Project;
use App\Models\Quote;
2020-10-02 08:33:55 +02:00
use App\Models\RecurringInvoice;
use App\Models\Task;
use App\Models\TaskStatus;
use App\Models\TaxRate;
use App\Models\User;
use App\Models\Vendor;
use App\Repositories\ClientContactRepository;
use App\Repositories\ClientRepository;
use App\Repositories\CompanyRepository;
use App\Repositories\CreditRepository;
2020-07-01 06:37:05 +02:00
use App\Repositories\Migration\InvoiceMigrationRepository;
use App\Repositories\Migration\PaymentMigrationRepository;
use App\Repositories\ProductRepository;
use App\Repositories\QuoteRepository;
use App\Repositories\UserRepository;
use App\Repositories\VendorContactRepository;
use App\Repositories\VendorRepository;
use App\Utils\Traits\CleanLineItems;
use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver;
use App\Utils\Traits\MakesHash;
2020-11-24 11:12:05 +01:00
use App\Utils\Traits\SavesDocuments;
2020-11-04 01:32:18 +01:00
use App\Utils\Traits\Uploadable;
2020-10-28 11:10:49 +01:00
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
2021-01-19 23:35:52 +01:00
use Illuminate\Http\UploadedFile;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
2020-10-07 05:00:32 +02:00
use Turbo124\Beacon\Facades\LightLogs;
2021-02-14 11:19:52 +01:00
use Illuminate\Support\Facades\Mail;
class Import implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
use CompanyGatewayFeesAndLimitsSaver;
use MakesHash;
use CleanLineItems;
2020-11-04 01:32:18 +01:00
use Uploadable;
2020-11-24 11:12:05 +01:00
use SavesDocuments;
/**
* @var array
*/
2020-11-24 06:11:20 +01:00
private $file_path; //the file path - using a different JSON parser here.
/**
* @var Company
*/
private $company;
2021-01-10 11:17:18 +01:00
private $token;
/**
* @var array
*/
private $available_imports = [
2020-12-14 22:52:14 +01:00
'account',
'company',
'users',
'payment_terms',
'tax_rates',
'clients',
2020-12-28 07:04:24 +01:00
'company_gateways',
'client_gateway_tokens',
'vendors',
2020-10-30 13:01:30 +01:00
'projects',
2020-10-06 02:52:16 +02:00
'products',
2020-11-23 13:55:04 +01:00
'credits',
2020-10-06 02:52:16 +02:00
'invoices',
2020-10-02 00:19:42 +02:00
'recurring_invoices',
2020-10-06 02:52:16 +02:00
'quotes',
2020-10-18 09:46:10 +02:00
'payments',
'expense_categories',
'task_statuses',
'expenses',
'tasks',
2021-01-09 12:10:04 +01:00
'documents',
];
/**
* @var User
*/
private $user;
/**
* Custom list of resources to be imported.
*
* @var array
*/
private $resources;
/**
* Local state manager for ids.
*
* @var array
*/
private $ids = [];
2020-11-18 11:46:36 +01:00
public $tries = 1;
2020-11-24 11:12:05 +01:00
public $timeout = 0;
2020-11-23 04:51:49 +01:00
// public $backoff = 86430;
2020-11-25 15:19:52 +01:00
// public $maxExceptions = 2;
/**
* Create a new job instance.
*
* @param array $data
* @param Company $company
* @param User $user
* @param array $resources
*/
2020-11-24 06:11:20 +01:00
public function __construct(string $file_path, Company $company, User $user, array $resources = [])
{
2020-11-24 06:11:20 +01:00
$this->file_path = $file_path;
$this->company = $company;
$this->user = $user;
$this->resources = $resources;
}
/**
* Execute the job.
*
2020-10-28 11:10:49 +01:00
* @return bool
*/
2020-11-25 15:19:52 +01:00
public function handle()
{
set_time_limit(0);
2020-11-30 08:43:33 +01:00
auth()->login($this->user, false);
auth()->user()->setCompany($this->company);
2020-11-25 15:19:52 +01:00
// $jsonStream = \JsonMachine\JsonMachine::fromFile($this->file_path, "/data");
2020-11-24 06:11:20 +01:00
$array = json_decode(file_get_contents($this->file_path), 1);
$data = $array['data'];
2020-11-23 13:55:04 +01:00
2020-11-24 06:11:20 +01:00
foreach ($this->available_imports as $import) {
if (! array_key_exists($import, $data)) {
//throw new ResourceNotAvailableForMigration("Resource {$key} is not available for migration.");
2020-11-24 06:11:20 +01:00
info("Resource {$import} is not available for migration.");
continue;
}
2020-11-24 06:11:20 +01:00
$method = sprintf('process%s', Str::ucfirst(Str::camel($import)));
2020-11-24 06:11:20 +01:00
info("Importing {$import}");
2020-11-24 06:11:20 +01:00
$this->{$method}($data[$import]);
}
2020-07-01 07:51:19 +02:00
$this->setInitialCompanyLedgerBalances();
2021-02-04 21:47:16 +01:00
// $this->fixClientBalances();
2021-02-10 04:18:23 +01:00
$check_data = CheckCompanyData::dispatchNow($this->company, md5(time()));
// if($check_data['status'] == 'errors')
// throw new ProcessingMigrationArchiveFailed(implode("\n", $check_data));
2021-03-16 12:29:16 +01:00
try{
Mail::to($this->user->email, $this->user->name())
->send(new MigrationCompleted($this->company, implode("<br>",$check_data)));
}
catch(\Exception $e) {
nlog($e->getMessage());
}
2020-07-19 09:17:19 +02:00
/*After a migration first some basic jobs to ensure the system is up to date*/
VersionCheck::dispatch();
CompanySizeCheck::dispatch();
info('Completed🚀🚀🚀🚀🚀 at '.now());
2021-04-06 00:19:27 +02:00
unlink($this->file_path);
}
2020-07-01 07:51:19 +02:00
private function setInitialCompanyLedgerBalances()
{
Client::cursor()->each(function ($client) {
2021-02-14 10:55:04 +01:00
$invoice_balances = $client->invoices->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance');
2020-07-01 07:51:19 +02:00
$company_ledger = CompanyLedgerFactory::create($client->company_id, $client->user_id);
$company_ledger->client_id = $client->id;
2021-02-14 10:55:04 +01:00
$company_ledger->adjustment = $invoice_balances;
2020-07-01 07:51:19 +02:00
$company_ledger->notes = 'Migrated Client Balance';
2021-02-14 10:55:04 +01:00
$company_ledger->balance = $invoice_balances;
$company_ledger->activity_id = Activity::CREATE_CLIENT;
2020-07-01 07:51:19 +02:00
$company_ledger->save();
$client->company_ledger()->save($company_ledger);
2021-02-14 10:55:04 +01:00
$client->balance = $invoice_balances;
$client->save();
2021-02-14 11:19:52 +01:00
2020-07-01 07:51:19 +02:00
});
}
2020-12-14 22:52:14 +01:00
private function processAccount(array $data) :void
{
2021-01-10 11:17:18 +01:00
if(array_key_exists('token', $data)){
$this->token = $data['token'];
unset($data['token']);
}
2020-12-14 22:52:14 +01:00
$account = $this->company->account;
$account->fill($data);
$account->save();
}
/**
* @param array $data
2020-10-28 11:10:49 +01:00
* @throws Exception
*/
private function processCompany(array $data): void
{
Company::unguard();
2020-10-28 11:10:49 +01:00
2020-09-10 13:01:10 +02:00
if (
$data['settings']['invoice_design_id'] > 9 ||
$data['settings']['invoice_design_id'] > "9"
) {
$data['settings']['invoice_design_id'] = 1;
}
$data = $this->transformCompanyData($data);
$rules = (new UpdateCompanyRequest())->rules();
$validator = Validator::make($data, $rules);
2021-02-04 01:07:21 +01:00
if ($validator->fails())
throw new MigrationValidatorFailed(json_encode($validator->errors()));
2021-02-04 01:07:21 +01:00
if (isset($data['account_id']))
unset($data['account_id']);
2021-02-04 01:07:21 +01:00
if(isset($data['version']))
unset($data['version']);
if (isset($data['referral_code'])) {
2020-08-28 03:06:46 +02:00
$account = $this->company->account;
$account->referral_code = $data['referral_code'];
$account->save();
unset($data['referral_code']);
}
$company_repository = new CompanyRepository();
$company_repository->save($data, $this->company);
2020-11-25 15:19:52 +01:00
if (isset($data['settings']->company_logo) && strlen($data['settings']->company_logo) > 0) {
2020-11-19 12:33:14 +01:00
try {
$tempImage = tempnam(sys_get_temp_dir(), basename($data['settings']->company_logo));
copy($data['settings']->company_logo, $tempImage);
$this->uploadLogo($tempImage, $this->company, $this->company);
2020-11-25 15:19:52 +01:00
} catch (\Exception $e) {
2020-11-19 12:33:14 +01:00
}
2020-11-04 01:32:18 +01:00
}
Company::reguard();
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
$rules = null;
$validator = null;
$company_repository = null;
}
private function transformCompanyData(array $data): array
{
$company_settings = CompanySettings::defaults();
if (array_key_exists('settings', $data)) {
foreach ($data['settings'] as $key => $value) {
if ($key == 'invoice_design_id' || $key == 'quote_design_id' || $key == 'credit_design_id') {
$value = $this->encodePrimaryKey($value);
}
if ($key == 'payment_terms' && $key = '') {
$value = -1;
}
$company_settings->{$key} = $value;
}
$data['settings'] = $company_settings;
}
return $data;
}
/**
* @param array $data
2020-10-28 11:10:49 +01:00
* @throws Exception
*/
private function processTaxRates(array $data): void
{
TaxRate::unguard();
$rules = [
'*.name' => 'required',
//'*.name' => 'required|distinct|unique:tax_rates,name,null,null,company_id,' . $this->company->id,
'*.rate' => 'required|numeric',
];
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
throw new MigrationValidatorFailed(json_encode($validator->errors()));
}
foreach ($data as $resource) {
$modified = $resource;
$company_id = $this->company->id;
$user_id = $this->processUserId($resource);
if (isset($resource['user_id'])) {
unset($resource['user_id']);
}
if (isset($resource['company_id'])) {
unset($resource['company_id']);
}
$tax_rate = TaxRateFactory::create($this->company->id, $user_id);
$tax_rate->fill($resource);
$tax_rate->save();
}
TaxRate::reguard();
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
$rules = null;
$validator = null;
}
/**
* @param array $data
2020-10-28 11:10:49 +01:00
* @throws Exception
*/
private function processUsers(array $data): void
{
User::unguard();
$rules = [
'*.first_name' => ['string'],
'*.last_name' => ['string'],
'*.email' => ['distinct'],
];
// if (config('ninja.db.multi_db_enabled')) {
// array_push($rules['*.email'], new ValidUserForCompany());
// }
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
throw new MigrationValidatorFailed(json_encode($validator->errors()));
}
$user_repository = new UserRepository();
foreach ($data as $resource) {
$modified = $resource;
unset($modified['id']);
2021-01-06 00:36:20 +01:00
unset($modified['password']); //cant import passwords.
$user = $user_repository->save($modified, $this->fetchUser($resource['email']), true, true);
2021-03-23 04:53:10 +01:00
$user->email_verified_at = now();
$user->confirmation_code = '';
$user->save();
$user_agent = array_key_exists('token_name', $resource) ?: request()->server('HTTP_USER_AGENT');
CreateCompanyToken::dispatchNow($this->company, $user, $user_agent);
$key = "users_{$resource['id']}";
$this->ids['users'][$key] = [
'old' => $resource['id'],
'new' => $user->id,
];
}
User::reguard();
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
$rules = null;
$validator = null;
$user_repository = null;
}
/**
* @param array $data
2020-10-28 11:10:49 +01:00
* @throws Exception
*/
private function processClients(array $data): void
{
Client::unguard();
$contact_repository = new ClientContactRepository();
$client_repository = new ClientRepository($contact_repository);
foreach ($data as $key => $resource) {
$modified = $resource;
$modified['company_id'] = $this->company->id;
$modified['user_id'] = $this->processUserId($resource);
2020-06-30 02:09:18 +02:00
$modified['balance'] = $modified['balance'] ?: 0;
2020-07-17 11:47:17 +02:00
$modified['paid_to_date'] = $modified['paid_to_date'] ?: 0;
unset($modified['id']);
unset($modified['contacts']);
$client = $client_repository->save(
$modified,
ClientFactory::create(
$this->company->id,
$modified['user_id']
)
);
2020-08-05 02:12:47 +02:00
$client->contacts()->forceDelete();
if (array_key_exists('contacts', $resource)) { // need to remove after importing new migration.json
$modified_contacts = $resource['contacts'];
foreach ($modified_contacts as $key => $client_contacts) {
$modified_contacts[$key]['company_id'] = $this->company->id;
$modified_contacts[$key]['user_id'] = $this->processUserId($resource);
$modified_contacts[$key]['client_id'] = $client->id;
$modified_contacts[$key]['password'] = 'mysuperpassword'; // @todo, and clean up the code..
unset($modified_contacts[$key]['id']);
}
$saveable_contacts['contacts'] = $modified_contacts;
$contact_repository->save($saveable_contacts, $client);
2020-12-29 00:44:24 +01:00
//link contact ids
2021-01-04 13:38:00 +01:00
foreach ($resource['contacts'] as $key => $old_contact) {
2021-01-19 23:35:52 +01:00
$contact_match = ClientContact::where('contact_key', $old_contact['contact_key'])
->where('company_id', $this->company->id)
->where('client_id', $client->id)
->withTrashed()
->first();
2020-12-29 00:44:24 +01:00
2021-01-04 13:38:00 +01:00
if ($contact_match) {
2021-01-19 23:35:52 +01:00
2020-12-29 00:44:24 +01:00
$this->ids['client_contacts']['client_contacts_'.$old_contact['id']] = [
'old' => $old_contact['id'],
'new' => $contact_match->id,
];
2021-01-19 23:35:52 +01:00
2020-12-29 00:44:24 +01:00
}
}
}
$key = "clients_{$resource['id']}";
$this->ids['clients'][$key] = [
'old' => $resource['id'],
'new' => $client->id,
];
}
Client::reguard();
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
$contact_repository = null;
$client_repository = null;
}
/**
* @param array $data
* @throws Exception
*/
private function processVendors(array $data): void
{
Vendor::unguard();
$contact_repository = new VendorContactRepository();
$vendor_repository = new VendorRepository($contact_repository);
foreach ($data as $key => $resource) {
$modified = $resource;
$modified['company_id'] = $this->company->id;
$modified['user_id'] = $this->processUserId($resource);
unset($modified['id']);
unset($modified['contacts']);
2020-10-31 01:46:00 +01:00
$vendor = $vendor_repository->save(
$modified,
VendorFactory::create(
$this->company->id,
$modified['user_id']
)
);
2020-10-31 01:46:00 +01:00
$vendor->contacts()->forceDelete();
if (array_key_exists('contacts', $resource)) { // need to remove after importing new migration.json
$modified_contacts = $resource['contacts'];
2020-10-31 01:46:00 +01:00
foreach ($modified_contacts as $key => $vendor_contacts) {
$modified_contacts[$key]['company_id'] = $this->company->id;
$modified_contacts[$key]['user_id'] = $this->processUserId($resource);
2020-10-31 06:35:05 +01:00
$modified_contacts[$key]['vendor_id'] = $vendor->id;
$modified_contacts[$key]['password'] = 'mysuperpassword'; // @todo, and clean up the code..
unset($modified_contacts[$key]['id']);
}
$saveable_contacts['contacts'] = $modified_contacts;
2020-10-31 01:46:00 +01:00
$contact_repository->save($saveable_contacts, $vendor);
}
$key = "vendors_{$resource['id']}";
$this->ids['vendors'][$key] = [
'old' => $resource['id'],
2020-10-31 01:46:00 +01:00
'new' => $vendor->id,
];
}
Vendor::reguard();
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
$contact_repository = null;
$client_repository = null;
}
private function processProducts(array $data): void
{
Product::unguard();
$rules = [
//'*.product_key' => 'required|distinct|unique:products,product_key,null,null,company_id,' . $this->company->id,
'*.cost' => 'numeric',
'*.price' => 'numeric',
'*.quantity' => 'numeric',
];
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
throw new MigrationValidatorFailed(json_encode($validator->errors()));
}
$product_repository = new ProductRepository();
foreach ($data as $resource) {
$modified = $resource;
$modified['company_id'] = $this->company->id;
$modified['user_id'] = $this->processUserId($resource);
unset($modified['id']);
$product_repository->save(
$modified,
ProductFactory::create(
$this->company->id,
$modified['user_id']
)
);
}
Product::reguard();
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
$product_repository = null;
}
2020-10-02 00:19:42 +02:00
private function processRecurringInvoices(array $data) :void
{
RecurringInvoice::unguard();
$rules = [
'*.client_id' => ['required'],
];
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
throw new MigrationValidatorFailed(json_encode($validator->errors()));
}
$invoice_repository = new InvoiceMigrationRepository();
foreach ($data as $key => $resource) {
$modified = $resource;
if (array_key_exists('client_id', $resource) && ! array_key_exists('clients', $this->ids)) {
throw new ResourceDependencyMissing('Processing invoices failed, because of missing dependency - clients.');
}
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
$modified['user_id'] = $this->processUserId($resource);
$modified['company_id'] = $this->company->id;
$modified['line_items'] = $this->cleanItems($modified['line_items']);
unset($modified['id']);
2021-01-04 13:38:00 +01:00
if (array_key_exists('invitations', $resource)) {
foreach ($resource['invitations'] as $key => $invite) {
2020-12-29 00:44:24 +01:00
$resource['invitations'][$key]['client_contact_id'] = $this->transformId('client_contacts', $invite['client_contact_id']);
$resource['invitations'][$key]['user_id'] = $modified['user_id'];
$resource['invitations'][$key]['company_id'] = $this->company->id;
unset($resource['invitations'][$key]['recurring_invoice_id']);
2021-04-16 05:58:14 +02:00
unset($resource['invitations'][$key]['id']);
2020-12-29 00:44:24 +01:00
}
2020-12-29 23:24:33 +01:00
2021-02-03 13:29:44 +01:00
$modified['invitations'] = $this->deDuplicateInvitations($resource['invitations']);
2020-12-29 22:58:48 +01:00
}
2020-10-02 00:19:42 +02:00
$invoice = $invoice_repository->save(
$modified,
RecurringInvoiceFactory::create($this->company->id, $modified['user_id'])
);
$key = "recurring_invoices_{$resource['id']}";
$this->ids['recurring_invoices'][$key] = [
'old' => $resource['id'],
'new' => $invoice->id,
];
}
RecurringInvoice::reguard();
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
$invoice_repository = null;
}
private function processInvoices(array $data): void
{
Invoice::unguard();
$rules = [
'*.client_id' => ['required'],
];
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
throw new MigrationValidatorFailed(json_encode($validator->errors()));
}
2020-07-01 06:37:05 +02:00
$invoice_repository = new InvoiceMigrationRepository();
foreach ($data as $key => $resource) {
$modified = $resource;
if (array_key_exists('client_id', $resource) && ! array_key_exists('clients', $this->ids)) {
throw new ResourceDependencyMissing('Processing invoices failed, because of missing dependency - clients.');
}
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
$modified['user_id'] = $this->processUserId($resource);
$modified['company_id'] = $this->company->id;
$modified['line_items'] = $this->cleanItems($modified['line_items']);
unset($modified['id']);
2020-12-29 00:44:24 +01:00
2021-01-04 13:38:00 +01:00
if (array_key_exists('invitations', $resource)) {
foreach ($resource['invitations'] as $key => $invite) {
2020-12-29 00:44:24 +01:00
$resource['invitations'][$key]['client_contact_id'] = $this->transformId('client_contacts', $invite['client_contact_id']);
$resource['invitations'][$key]['user_id'] = $modified['user_id'];
$resource['invitations'][$key]['company_id'] = $this->company->id;
unset($resource['invitations'][$key]['invoice_id']);
2021-04-16 05:58:14 +02:00
unset($resource['invitations'][$key]['id']);
2020-12-29 00:44:24 +01:00
}
2021-02-03 13:29:44 +01:00
$modified['invitations'] = $this->deDuplicateInvitations($resource['invitations']);
2020-12-29 21:37:48 +01:00
}
2021-02-03 13:29:44 +01:00
$invoice = $invoice_repository->save(
$modified,
InvoiceFactory::create($this->company->id, $modified['user_id'])
);
$key = "invoices_{$resource['id']}";
$this->ids['invoices'][$key] = [
'old' => $resource['id'],
'new' => $invoice->id,
];
}
Invoice::reguard();
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
$invoice_repository = null;
}
2021-02-03 13:29:44 +01:00
/* Prevent edge case where V4 has inserted multiple invitations for a resource for a client contact */
private function deDuplicateInvitations($invitations)
{
return array_intersect_key($invitations, array_unique(array_column($invitations, 'client_contact_id')));
}
private function processCredits(array $data): void
{
Credit::unguard();
$rules = [
'*.client_id' => ['required'],
];
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
throw new MigrationValidatorFailed(json_encode($validator->errors()));
}
$credit_repository = new CreditRepository();
foreach ($data as $resource) {
$modified = $resource;
if (array_key_exists('client_id', $resource) && ! array_key_exists('clients', $this->ids)) {
throw new ResourceDependencyMissing('Processing credits failed, because of missing dependency - clients.');
}
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
$modified['user_id'] = $this->processUserId($resource);
$modified['company_id'] = $this->company->id;
unset($modified['id']);
$credit = $credit_repository->save(
$modified,
CreditFactory::create($this->company->id, $modified['user_id'])
);
2021-02-04 21:47:16 +01:00
//remove credit balance from ledger
if($credit->balance > 0 && $credit->client->balance > 0){
$client = $credit->client;
$client->balance -= $credit->balance;
$client->save();
}
$key = "credits_{$resource['id']}";
$this->ids['credits'][$key] = [
'old' => $resource['id'],
'new' => $credit->id,
];
}
Credit::reguard();
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
$credit_repository = null;
}
private function processQuotes(array $data): void
{
Quote::unguard();
$rules = [
'*.client_id' => ['required'],
];
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
throw new MigrationValidatorFailed(json_encode($validator->errors()));
}
$quote_repository = new QuoteRepository();
foreach ($data as $resource) {
$modified = $resource;
if (array_key_exists('client_id', $resource) && ! array_key_exists('clients', $this->ids)) {
throw new ResourceDependencyMissing('Processing quotes failed, because of missing dependency - clients.');
}
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
$modified['user_id'] = $this->processUserId($resource);
$modified['company_id'] = $this->company->id;
unset($modified['id']);
2021-02-03 13:29:44 +01:00
if (array_key_exists('invitations', $resource)) {
foreach ($resource['invitations'] as $key => $invite) {
$resource['invitations'][$key]['client_contact_id'] = $this->transformId('client_contacts', $invite['client_contact_id']);
$resource['invitations'][$key]['user_id'] = $modified['user_id'];
$resource['invitations'][$key]['company_id'] = $this->company->id;
unset($resource['invitations'][$key]['invoice_id']);
2021-04-16 05:58:14 +02:00
unset($resource['invitations'][$key]['id']);
2021-02-03 13:29:44 +01:00
}
$modified['invitations'] = $this->deDuplicateInvitations($resource['invitations']);
}
2020-11-23 22:00:59 +01:00
$quote = $quote_repository->save(
$modified,
QuoteFactory::create($this->company->id, $modified['user_id'])
);
$old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id;
2020-11-23 22:00:59 +01:00
$key = "quotes_{$resource['id']}";
$this->ids['quotes'][$key] = [
'old' => $resource['id'],
2020-11-23 22:00:59 +01:00
'new' => $quote->id,
];
}
Quote::reguard();
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
$quote_repository = null;
}
private function processPayments(array $data): void
2020-11-23 22:33:37 +01:00
{
Payment::reguard();
$rules = [
'*.amount' => ['required'],
'*.client_id' => ['required'],
];
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
throw new MigrationValidatorFailed(json_encode($validator->errors()));
}
2020-07-01 06:37:05 +02:00
$payment_repository = new PaymentMigrationRepository(new CreditRepository());
foreach ($data as $resource) {
$modified = $resource;
if (array_key_exists('client_id', $resource) && ! array_key_exists('clients', $this->ids)) {
throw new ResourceDependencyMissing('Processing payments failed, because of missing dependency - clients.');
}
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
$modified['user_id'] = $this->processUserId($resource);
//$modified['invoice_id'] = $this->transformId('invoices', $resource['invoice_id']);
$modified['company_id'] = $this->company->id;
//unset($modified['invoices']);
unset($modified['invoice_id']);
if (isset($modified['invoices'])) {
2020-07-01 06:37:05 +02:00
foreach ($modified['invoices'] as $key => $invoice) {
2020-11-25 15:19:52 +01:00
if ($this->tryTransformingId('invoices', $invoice['invoice_id'])) {
2020-10-18 09:46:10 +02:00
$modified['invoices'][$key]['invoice_id'] = $this->transformId('invoices', $invoice['invoice_id']);
2020-11-25 15:19:52 +01:00
} else {
2021-02-04 11:45:11 +01:00
nlog($modified['invoices']);
// $modified['credits'][$key]['credit_id'] = $this->transformId('credits', $invoice['invoice_id']);
// $modified['credits'][$key]['amount'] = $modified['invoices'][$key]['amount'];
}
}
}
$payment = $payment_repository->save(
$modified,
PaymentFactory::create($this->company->id, $modified['user_id'])
);
2021-01-19 23:35:52 +01:00
if (array_key_exists('company_gateway_id', $resource) && isset($resource['company_gateway_id']) && $resource['company_gateway_id'] != 'NULL') {
2020-12-28 07:04:24 +01:00
$payment->company_gateway_id = $this->transformId('company_gateways', $resource['company_gateway_id']);
$payment->save();
}
$old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id;
$this->ids['payments'] = [
"payments_{$old_user_key}" => [
'old' => $old_user_key,
'new' => $payment->id,
],
];
2021-02-07 13:35:16 +01:00
if(in_array($payment->status_id, [Payment::STATUS_REFUNDED, Payment::STATUS_PARTIALLY_REFUNDED])) {
$this->processPaymentRefund($payment);
}
}
Payment::reguard();
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
$payment_repository = null;
}
2021-02-07 13:35:16 +01:00
private function processPaymentRefund($payment)
{
$invoices = $payment->invoices()->get();
$invoices->each(function ($invoice) use($payment) {
if ($payment->refunded > 0 && in_array($invoice->status_id, [Invoice::STATUS_SENT])) {
$invoice->service()
->updateBalance($payment->refunded)
->updatePaidToDate($payment->refunded*-1)
->updateStatus()
->save();
}
});
}
private function updatePaymentForStatus($payment, $status_id) :Payment
{
// define('PAYMENT_STATUS_PENDING', 1);
// define('PAYMENT_STATUS_VOIDED', 2);
// define('PAYMENT_STATUS_FAILED', 3);
// define('PAYMENT_STATUS_COMPLETED', 4);
// define('PAYMENT_STATUS_PARTIALLY_REFUNDED', 5);
// define('PAYMENT_STATUS_REFUNDED', 6);
switch ($status_id) {
case 1:
return $payment;
break;
case 2:
return $payment->service()->deletePayment();
break;
case 3:
return $payment->service()->deletePayment();
break;
case 4:
return $payment;
break;
case 5:
$payment->status_id = Payment::STATUS_PARTIALLY_REFUNDED;
$payment->save();
return $payment;
2020-11-25 15:19:52 +01:00
break;
case 6:
$payment->status_id = Payment::STATUS_REFUNDED;
$payment->save();
return $payment;
2020-11-25 15:19:52 +01:00
break;
default:
return $payment;
break;
}
}
private function processDocuments(array $data): void
{
2020-11-25 15:19:52 +01:00
// Document::unguard();
/* No validators since data provided by database is already valid. */
2020-11-25 15:19:52 +01:00
foreach ($data as $resource) {
2021-02-03 13:29:44 +01:00
$modified = $resource;
if (array_key_exists('invoice_id', $resource) && $resource['invoice_id'] && ! array_key_exists('invoices', $this->ids)) {
2021-02-09 15:15:26 +01:00
return;
//throw new ResourceDependencyMissing('Processing documents failed, because of missing dependency - invoices.');
}
if (array_key_exists('expense_id', $resource) && $resource['expense_id'] && ! array_key_exists('expenses', $this->ids)) {
2021-02-09 15:15:26 +01:00
return;
//throw new ResourceDependencyMissing('Processing documents failed, because of missing dependency - expenses.');
}
if (array_key_exists('invoice_id', $resource) && $resource['invoice_id'] && array_key_exists('invoices', $this->ids)) {
2021-02-14 10:25:19 +01:00
$try_quote = false;
2021-02-14 10:55:04 +01:00
$exception = false;
2021-02-14 10:25:19 +01:00
try{
$invoice_id = $this->transformId('invoices', $resource['invoice_id']);
$entity = Invoice::where('id', $invoice_id)->withTrashed()->first();
}
catch(\Exception $e){
2021-02-14 10:55:04 +01:00
nlog("i couldn't find the invoice document {$resource['invoice_id']}, perhaps it is a quote?");
nlog($e->getMessage());
2021-02-14 10:25:19 +01:00
$try_quote = true;
}
2021-02-14 10:55:04 +01:00
if($try_quote && array_key_exists('quotes', $this->ids) ) {
2021-02-14 10:25:19 +01:00
$quote_id = $this->transformId('quotes', $resource['invoice_id']);
$entity = Quote::where('id', $quote_id)->withTrashed()->first();
$exception = $e;
}
2021-02-14 10:55:04 +01:00
if(!$entity)
2021-02-14 10:25:19 +01:00
throw new Exception("Resource invoice/quote document not available.");
}
2021-02-14 10:25:19 +01:00
if (array_key_exists('expense_id', $resource) && $resource['expense_id'] && array_key_exists('expenses', $this->ids)) {
2020-11-24 11:12:05 +01:00
$expense_id = $this->transformId('expenses', $resource['expense_id']);
$entity = Expense::where('id', $expense_id)->withTrashed()->first();
}
2021-01-10 11:17:18 +01:00
$file_url = $resource['url'];
$file_name = $resource['name'];
$file_path = sys_get_temp_dir().'/'.$file_name;
2021-02-03 13:29:44 +01:00
try {
file_put_contents($file_path, $this->curlGet($file_url));
$finfo = new \finfo(FILEINFO_MIME_TYPE);
$file_info = $finfo->file($file_path);
$uploaded_file = new UploadedFile(
$file_path,
$file_name,
$file_info,
filesize($file_path),
0,
false
);
$this->saveDocument($uploaded_file, $entity, $is_public = true);
}
catch(\Exception $e) {
2020-11-24 11:12:05 +01:00
2021-02-03 13:29:44 +01:00
//do nothing, gracefully :)
}
2020-11-24 11:12:05 +01:00
2021-01-09 12:10:04 +01:00
}
2020-11-24 11:12:05 +01:00
}
private function processPaymentTerms(array $data) :void
{
PaymentTerm::unguard();
$modified = collect($data)->map(function ($item) {
$item['user_id'] = $this->user->id;
$item['company_id'] = $this->company->id;
2020-12-22 22:38:13 +01:00
$item['is_deleted'] = isset($item['is_deleted']) ? $item['is_deleted'] : 0;
return $item;
})->toArray();
PaymentTerm::insert($modified);
PaymentTerm::reguard();
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
}
private function processCompanyGateways(array $data) :void
{
CompanyGateway::unguard();
$rules = [
'*.gateway_key' => 'required',
'*.fees_and_limits' => new ValidCompanyGatewayFeesAndLimitsRule(),
];
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
throw new MigrationValidatorFailed(json_encode($validator->errors()));
}
foreach ($data as $resource) {
$modified = $resource;
$modified['user_id'] = $this->processUserId($resource);
$modified['company_id'] = $this->company->id;
unset($modified['id']);
if (isset($modified['config'])) {
$modified['config'] = encrypt($modified['config']);
}
if (isset($modified['fees_and_limits'])) {
$modified['fees_and_limits'] = $this->cleanFeesAndLimits($modified['fees_and_limits']);
}
$company_gateway = CompanyGateway::create($modified);
2020-12-28 07:04:24 +01:00
$key = "company_gateways_{$resource['id']}";
2020-12-28 07:04:24 +01:00
$this->ids['company_gateways'][$key] = [
'old' => $resource['id'],
'new' => $company_gateway->id,
];
}
CompanyGateway::reguard();
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
}
private function processClientGatewayTokens(array $data) :void
{
ClientGatewayToken::unguard();
foreach ($data as $resource) {
$modified = $resource;
unset($modified['id']);
$modified['company_id'] = $this->company->id;
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
2020-11-19 12:33:14 +01:00
//$modified['user_id'] = $this->processUserId($resource);
$cgt = ClientGatewayToken::Create($modified);
2020-12-13 11:33:30 +01:00
$key = "client_gateway_tokens_{$resource['id']}";
2020-12-13 11:33:30 +01:00
$this->ids['client_gateway_tokens'][$key] = [
'old' => $resource['id'],
'new' => $cgt->id,
];
}
ClientGatewayToken::reguard();
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
}
private function processTaskStatuses(array $data) :void
2020-11-25 15:19:52 +01:00
{
info('in task statuses');
TaskStatus::unguard();
foreach ($data as $resource) {
$modified = $resource;
unset($modified['id']);
$modified['company_id'] = $this->company->id;
2020-11-07 10:08:21 +01:00
$modified['user_id'] = $this->processUserId($resource);
$task_status = TaskStatus::Create($modified);
2020-12-13 11:33:30 +01:00
$key = "task_statuses_{$resource['id']}";
2020-12-13 11:33:30 +01:00
$this->ids['task_statuses'][$key] = [
'old' => $resource['id'],
'new' => $task_status->id,
];
}
TaskStatus::reguard();
$data = null;
2020-10-30 13:16:19 +01:00
info('finished task statuses');
}
private function processExpenseCategories(array $data) :void
{
ExpenseCategory::unguard();
foreach ($data as $resource) {
$modified = $resource;
unset($modified['id']);
$modified['company_id'] = $this->company->id;
2020-11-07 10:08:21 +01:00
$modified['user_id'] = $this->processUserId($resource);
$expense_category = ExpenseCategory::Create($modified);
$old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id;
2020-11-28 07:22:43 +01:00
$key = "expense_categories_{$resource['id']}";
$this->ids['expense_categories'][$key] = [
'old' => $resource['id'],
2021-01-09 12:10:04 +01:00
'new' => $expense_category->id,
];
2020-11-28 07:22:43 +01:00
// $this->ids['expense_categories'] = [
// "expense_categories_{$old_user_key}" => [
// 'old' => $resource['id'],
// 'new' => $expense_category->id,
// ],
// ];
}
ExpenseCategory::reguard();
$data = null;
}
private function processTasks(array $data) :void
{
Task::unguard();
foreach ($data as $resource) {
$modified = $resource;
unset($modified['id']);
$modified['company_id'] = $this->company->id;
2020-11-07 10:08:21 +01:00
$modified['user_id'] = $this->processUserId($resource);
2020-10-31 01:46:00 +01:00
2020-11-25 15:19:52 +01:00
if (isset($modified['client_id'])) {
2020-10-31 01:46:00 +01:00
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
2020-11-25 15:19:52 +01:00
}
2020-10-31 01:46:00 +01:00
2020-11-25 15:19:52 +01:00
if (isset($modified['invoice_id'])) {
2020-10-31 01:46:00 +01:00
$modified['invoice_id'] = $this->transformId('invoices', $resource['invoice_id']);
2020-11-25 15:19:52 +01:00
}
2020-10-31 01:46:00 +01:00
2020-11-25 15:19:52 +01:00
if (isset($modified['project_id'])) {
2020-10-31 01:46:00 +01:00
$modified['project_id'] = $this->transformId('projects', $resource['project_id']);
2020-11-25 15:19:52 +01:00
}
2020-10-31 01:46:00 +01:00
2020-11-25 15:19:52 +01:00
if (isset($modified['status_id'])) {
2020-10-31 01:46:00 +01:00
$modified['status_id'] = $this->transformId('task_statuses', $resource['status_id']);
2020-11-25 15:19:52 +01:00
}
$task = Task::Create($modified);
$old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id;
$this->ids['tasks'] = [
"tasks_{$old_user_key}" => [
'old' => $resource['id'],
'new' => $task->id,
],
];
}
Task::reguard();
$data = null;
}
2020-10-30 13:01:30 +01:00
private function processProjects(array $data) :void
{
Project::unguard();
foreach ($data as $resource) {
$modified = $resource;
unset($modified['id']);
$modified['company_id'] = $this->company->id;
2020-11-07 10:08:21 +01:00
$modified['user_id'] = $this->processUserId($resource);
2020-10-31 01:46:00 +01:00
2020-11-25 15:19:52 +01:00
if (isset($modified['client_id'])) {
2020-10-31 01:46:00 +01:00
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
2020-11-25 15:19:52 +01:00
}
2020-10-30 13:01:30 +01:00
$project = Project::Create($modified);
2020-11-23 22:00:59 +01:00
$key = "projects_{$resource['id']}";
2020-10-30 13:01:30 +01:00
2020-11-23 22:00:59 +01:00
$this->ids['projects'][$key] = [
'old' => $resource['id'],
'new' => $project->id,
2020-10-30 13:01:30 +01:00
];
}
Project::reguard();
$data = null;
}
2020-10-30 13:22:32 +01:00
private function processExpenses(array $data) :void
{
Expense::unguard();
foreach ($data as $resource) {
$modified = $resource;
unset($modified['id']);
$modified['company_id'] = $this->company->id;
2020-11-07 10:08:21 +01:00
$modified['user_id'] = $this->processUserId($resource);
2020-11-25 15:19:52 +01:00
if (isset($resource['client_id'])) {
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
2020-11-25 15:19:52 +01:00
}
2020-11-25 15:19:52 +01:00
if (isset($resource['category_id'])) {
$modified['category_id'] = $this->transformId('expense_categories', $resource['category_id']);
2020-11-25 15:19:52 +01:00
}
2020-11-25 15:19:52 +01:00
if (isset($resource['invoice_id'])) {
$modified['invoice_id'] = $this->transformId('invoices', $resource['invoice_id']);
2020-11-25 15:19:52 +01:00
}
2020-11-25 15:19:52 +01:00
if (isset($resource['project_id'])) {
$modified['project_id'] = $this->transformId('projects', $resource['project_id']);
2020-11-25 15:19:52 +01:00
}
2020-10-31 06:35:05 +01:00
2020-11-25 15:19:52 +01:00
if (isset($resource['vendor_id'])) {
2020-10-31 06:35:05 +01:00
$modified['vendor_id'] = $this->transformId('vendors', $resource['vendor_id']);
2020-11-25 15:19:52 +01:00
}
2020-10-30 13:22:32 +01:00
$expense = Expense::Create($modified);
$old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id;
2021-01-09 12:10:04 +01:00
$key = "expenses_{$resource['id']}";
$this->ids['expenses'][$key] = [
'old' => $resource['id'],
'new' => $expense->id,
2020-10-30 13:22:32 +01:00
];
2021-01-09 12:10:04 +01:00
2020-10-30 13:22:32 +01:00
}
Expense::reguard();
$data = null;
}
/**
* |--------------------------------------------------------------------------
* | Additional migration methods.
* |--------------------------------------------------------------------------
* |
* | These methods aren't initialized automatically, so they don't depend on
* | the migration data.
*/
/**
* Cloned from App\Http\Requests\User\StoreUserRequest.
*
* @param string $data
* @return User
*/
public function fetchUser(string $data): User
{
$user = MultiDB::hasUser(['email' => $data]);
if (! $user) {
$user = UserFactory::create($this->company->account->id);
}
2020-11-17 10:53:32 +01:00
return $user;
}
/**
* @param string $resource
* @param string $old
* @return int
2020-10-28 11:10:49 +01:00
* @throws Exception
*/
public function transformId($resource, string $old): int
{
if (! array_key_exists($resource, $this->ids)) {
2020-11-25 15:19:52 +01:00
info(print_r($resource, 1));
2020-10-28 11:10:49 +01:00
throw new Exception("Resource {$resource} not available.");
}
if (! array_key_exists("{$resource}_{$old}", $this->ids[$resource])) {
2020-10-28 11:10:49 +01:00
throw new Exception("Missing resource key: {$resource}_{$old}");
}
return $this->ids[$resource]["{$resource}_{$old}"]['new'];
}
2020-11-25 03:59:23 +01:00
private function tryTransformingId($resource, string $old): ?int
{
if (! array_key_exists($resource, $this->ids)) {
return false;
}
if (! array_key_exists("{$resource}_{$old}", $this->ids[$resource])) {
return false;
}
return $this->ids[$resource]["{$resource}_{$old}"]['new'];
}
/**
* Process & handle user_id.
*
* @param array $resource
* @return int|mixed
2020-10-28 11:10:49 +01:00
* @throws Exception
*/
public function processUserId(array $resource)
{
if (! array_key_exists('user_id', $resource)) {
return $this->user->id;
}
if (array_key_exists('user_id', $resource) && ! array_key_exists('users', $this->ids)) {
return $this->user->id;
}
return $this->transformId('users', $resource['user_id']);
}
public function failed($exception = null)
{
info('the job failed');
2020-10-07 05:00:32 +02:00
$job_failure = new MigrationFailure();
$job_failure->string_metric5 = get_class($this);
$job_failure->string_metric6 = $exception->getMessage();
LightLogs::create($job_failure)
->batch();
info(print_r($exception->getMessage(), 1));
}
2021-01-10 11:17:18 +01:00
public function curlGet($url, $headers = false)
{
return $this->exec('GET', $url, null);
}
public function exec($method, $url, $data)
{
nlog($this->token);
$client = new \GuzzleHttp\Client(['headers' =>
[
'X-Ninja-Token' => $this->token,
]
]);
$response = $client->request('GET', $url);
return $response->getBody();
}
/* In V4 we use negative invoices (credits) and add then into the client balance. In V5, these sit off ledger and are applied later.
This next section will check for credit balances and reduce the client balance so that the V5 balances are correct
*/
2021-02-04 21:47:16 +01:00
// private function fixClientBalances()
// {
2021-02-04 21:47:16 +01:00
// Client::cursor()->each(function ($client) {
2021-02-04 21:47:16 +01:00
// $credit_balance = $client->credits->where('is_deleted', false)->sum('balance');
2021-02-04 21:47:16 +01:00
// if($credit_balance > 0){
// $client->balance += $credit_balance;
// $client->save();
// }
2021-02-04 21:47:16 +01:00
// });
2021-02-04 21:47:16 +01:00
// }
}