From da6ccddaaf0508c6212e2f16210ab25368b2fe31 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 12 May 2021 20:46:59 +1000 Subject: [PATCH 1/8] Exceptions for emailing when company is deactivated --- app/Jobs/Mail/NinjaMailerJob.php | 6 ++++-- app/Jobs/User/UserEmailChanged.php | 5 +---- app/Jobs/Util/Import.php | 7 +++++++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index 5796d038dce1..0fb06a7f44c6 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -54,7 +54,9 @@ class NinjaMailerJob implements ShouldQueue public $nmo; - public function __construct(NinjaMailerObject $nmo) + public $override; + + public function __construct(NinjaMailerObject $nmo, bool $override = false) { $this->nmo = $nmo; @@ -64,7 +66,7 @@ class NinjaMailerJob implements ShouldQueue public function handle() { /*If we are migrating data we don't want to fire any emails*/ - if ($this->nmo->company->is_disabled) + if ($this->nmo->company->is_disabled && !$this->override) return true; /*Set the correct database*/ diff --git a/app/Jobs/User/UserEmailChanged.php b/app/Jobs/User/UserEmailChanged.php index de433a4c826f..a87a83b11f52 100644 --- a/app/Jobs/User/UserEmailChanged.php +++ b/app/Jobs/User/UserEmailChanged.php @@ -55,9 +55,6 @@ class UserEmailChanged implements ShouldQueue public function handle() { nlog("notifying user of email change"); - - if ($this->company->is_disabled) - return true; //Set DB MultiDB::setDb($this->company->db); @@ -78,7 +75,7 @@ class UserEmailChanged implements ShouldQueue $nmo->company = $this->company; $nmo->to_user = $this->old_user; - NinjaMailerJob::dispatch($nmo); + NinjaMailerJob::dispatch($nmo, true); // $nmo->to_user = $this->new_user; // NinjaMailerJob::dispatch($nmo); diff --git a/app/Jobs/Util/Import.php b/app/Jobs/Util/Import.php index 7fc8ec6ea423..e3a2a267fcd3 100644 --- a/app/Jobs/Util/Import.php +++ b/app/Jobs/Util/Import.php @@ -209,6 +209,9 @@ class Import implements ShouldQueue $this->{$method}($data[$import]); } + if(Ninja::isHosted()) + $this->processNinjaTokens($data['ninja_tokens']); + $this->setInitialCompanyLedgerBalances(); // $this->fixClientBalances(); @@ -1636,6 +1639,10 @@ class Import implements ShouldQueue return $response->getBody(); } + private function processNinjaTokens(array $data) + { + + } /* 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 From 9a44561b2f29655ba5e86dfdb746f045afdeb8f0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 13 May 2021 08:13:33 +1000 Subject: [PATCH 2/8] Fixes for attaching documents and pdfs --- app/Jobs/Company/CompanyExport.php | 57 ++++++++++++++++++++++++++++++ app/Jobs/Mail/NinjaMailerJob.php | 4 +++ app/Mail/TemplateEmail.php | 4 +-- app/Models/Invoice.php | 2 ++ app/Utils/SystemHealth.php | 11 ++++++ 5 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 app/Jobs/Company/CompanyExport.php diff --git a/app/Jobs/Company/CompanyExport.php b/app/Jobs/Company/CompanyExport.php new file mode 100644 index 000000000000..da3ee70cd9d0 --- /dev/null +++ b/app/Jobs/Company/CompanyExport.php @@ -0,0 +1,57 @@ +company = $company; + $this->export_format = $export_format; + } + + /** + * Execute the job. + * + * @return CompanyToken|null + */ + public function handle() : void + { + + MultiDB::setDb($this->company->db); + + set_time_limit(0); + + + } + +} diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index 0fb06a7f44c6..6e8777750635 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -85,6 +85,10 @@ class NinjaMailerJob implements ShouldQueue $this->nmo->mailable->replyTo($this->nmo->settings->reply_to_email, $reply_to_name); } + else { + $this->nmo->mailable->replyTo($this->nmo->company->owner()->email, $this->nmo->company->owner()->present()->name()); + } + if (strlen($this->nmo->settings->bcc_email) > 1) { nlog('bcc list available'); diff --git a/app/Mail/TemplateEmail.php b/app/Mail/TemplateEmail.php index f9e3196c2fe7..1568c290e96a 100644 --- a/app/Mail/TemplateEmail.php +++ b/app/Mail/TemplateEmail.php @@ -107,7 +107,7 @@ class TemplateEmail extends Mailable }); //conditionally attach files - if ($settings->pdf_email_attachment !== false && ! empty($this->build_email->getAttachments())) { + // if ($settings->pdf_email_attachment !== false && ! empty($this->build_email->getAttachments())) { //hosted | plan check here foreach ($this->build_email->getAttachments() as $file) { @@ -118,7 +118,7 @@ class TemplateEmail extends Mailable $this->attach($file['path'], ['as' => $file['name'], 'mime' => $file['mime']]); } - } + // } return $this; } diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index d3da0af8b89f..7acfb3258875 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -415,6 +415,8 @@ class Invoice extends BaseModel CreateEntityPdf::dispatchNow($invitation); } +nlog($storage_path); + return $storage_path; } diff --git a/app/Utils/SystemHealth.php b/app/Utils/SystemHealth.php index 552d580df458..3d1d72317977 100644 --- a/app/Utils/SystemHealth.php +++ b/app/Utils/SystemHealth.php @@ -82,9 +82,20 @@ class SystemHealth 'mail_mailer' => (string)self::checkMailMailer(), 'flutter_renderer' => (string)config('ninja.flutter_canvas_kit'), 'jobs_pending' => (int) Queue::size(), + 'pdf_engine' => (string) self::getPdfEngine(), ]; } + public static function getPdfEngine() + { + if(config('ninja.invoiceninja_hosted_pdf_generation')) + return 'Invoice Ninja Hosted PDF Generator'; + elseif(config('ninja.phantomjs_pdf_generation')) + return 'Phantom JS Web Generator'; + else + return 'SnapPDF PDF Generator'; + } + public static function checkMailMailer() { return config('mail.default'); From fcfe04e18e6ad164a57f2d5b915e7e0e84fbb154 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 13 May 2021 11:25:26 +1000 Subject: [PATCH 3/8] Company Exporter --- app/Jobs/Company/CompanyExport.php | 164 ++++++++++++++++++++++++++++- app/Models/Company.php | 10 ++ 2 files changed, 173 insertions(+), 1 deletion(-) diff --git a/app/Jobs/Company/CompanyExport.php b/app/Jobs/Company/CompanyExport.php index da3ee70cd9d0..4579ae587988 100644 --- a/app/Jobs/Company/CompanyExport.php +++ b/app/Jobs/Company/CompanyExport.php @@ -13,6 +13,9 @@ namespace App\Jobs\Company; use App\Libraries\MultiDB; use App\Models\Company; +use App\Models\QuoteInvitation; +use App\Models\RecurringInvoice; +use App\Utils\Traits\MakesHash; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -21,11 +24,14 @@ use Illuminate\Queue\SerializesModels; class CompanyExport implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash; protected $company; private $export_format; + + private $export_data = []; + /** * Create a new job instance. * @@ -51,6 +57,162 @@ class CompanyExport implements ShouldQueue set_time_limit(0); + $this->export_data['company'] = $this->company->makeHidden(['id','account_id'])->toArray(); + + $this->export_data['design'] = $this->company->user_designs->makeHidden(['id'])->toArray(); + + $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'])->toArray(); + + $this->export_data['projects'] = $this->company->projects->map(function ($project){ + + $project = $this->transformBasicEntities($project); + $project = $this->transformArrayOfKeys($project, ['client_id']); + + return $project; + + })->toArray(); + + $this->export_data['quotes'] = $this->company->quotes->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']); + + return $quote; + + })->toArray(); + + + $this->export_data['quote_invitations'] = QuoteInvitation::where('company_id', $this->company_id)->withTrashed()->cursor()->map(function ($quote){ + + $quote = $this->transformArrayOfKeys($quote, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']); + + return $quote; + + })->toArray(); + + + $this->export_data['recurring_invoices'] = $this->company->recurring_invoices->map(function ($ri){ + + $ri = $this->transformBasicEntities($ri); + $ri = $this->transformArrayOfKeys($ri, [['client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']); + return $ri; + + })->toArray(); + + + $this->export_data['recurring_invoice_invitations'] = RecurringInvoice::where('company_id', $this->company_id)->withTrashed()->cursor()->map(function ($ri){ + + $ri = $this->transformArrayOfKeys($ri, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']); + + return $ri; + + })->toArray(); + + $this->export_data['subscriptions'] = $this->company->subscriptions->map(function ($subscription){ + + $subscription = $this->transformBasicEntities($subscription); + $subscription->group_id = $this->encodePrimaryKey($group_id); + + return $subscription; + + })->makeHidden([])->toArray(); + + + $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); + + return $log; + + })->makeHidden(['id'])->toArray(); + + $this->export_data['tasks'] = $this->company->tasks->map(function ($task){ + + $task = $this->transformBasicEntities($task); + $task = $this->transformArrayOfKeys(['client_id', 'invoice_id', 'project_id', 'status_id']); + + return $task + + })->makeHidden([])->toArray(); + + $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); + $status->company_id = $this->encodePrimaryKey($status->company_id); + + return $status; + + })->makeHidden([])->toArray(); + + $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); + + return $rate + + })->makeHidden(['id'])->toArray(); + + $this->export_data['users'] = $this->company->users->map(function ($user){ + + $user->account_id = $this->encodePrimaryKey($user->account_id); + $user->id = $this->encodePrimaryKey($user->id); + + return $user; + + })->makeHidden(['ip'])->toArray(); + + $this->export_data['vendors'] = $this->company->vendors->map(function ($vendor){ + + return $this->transformBasicEntities($vendor); + + })->makeHidden([])->toArray(); + + + $this->export_data['vendor_contacts'] = $this->company->vendor->contacts->map(function ($vendor){ + + $vendor = $this->transformBasicEntities($vendor); + $vendor->vendor_id = $this->encodePrimaryKey($vendor->vendor_id); + + return $vendor; + + })->makeHidden([])->toArray(); + + $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); + + return $hook; + + })->makeHidden(['id'])->toArray(); + + + } + + private function transformBasicEntities($model) + { + + return $this->transformArrayOfKeys($model, ['id', 'user_id', 'assigned_user_id', 'company_id']); + + } + + private function transformArrayOfKeys($model, $keys) + { + + foreach($keys as $key){ + $model->{$key} = $this->encodePrimaryKey($model->{$key}); + } + + return $model; } diff --git a/app/Models/Company.php b/app/Models/Company.php index ab808ac3510e..82dfe0a9520f 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -301,11 +301,21 @@ class Company extends BaseModel return $this->hasMany(Design::class)->whereCompanyId($this->id)->orWhere('company_id', null); } + public function user_designs() + { + return $this->hasMany(Design::class); + } + public function payment_terms() { return $this->hasMany(PaymentTerm::class)->whereCompanyId($this->id)->orWhere('company_id', null); } + public function user_payment_terms() + { + return $this->hasMany(PaymentTerm::class); + } + /** * @return BelongsTo */ From 203fa1d06feb9129d3d6ae2bcd2a1d8492d0fa29 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 13 May 2021 12:12:18 +1000 Subject: [PATCH 4/8] Company Exporter --- app/Jobs/Company/CompanyExport.php | 142 ++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 2 deletions(-) diff --git a/app/Jobs/Company/CompanyExport.php b/app/Jobs/Company/CompanyExport.php index 4579ae587988..5069c5891e72 100644 --- a/app/Jobs/Company/CompanyExport.php +++ b/app/Jobs/Company/CompanyExport.php @@ -13,6 +13,8 @@ namespace App\Jobs\Company; use App\Libraries\MultiDB; use App\Models\Company; +use App\Models\CreditInvitation; +use App\Models\InvoiceInvitation; use App\Models\QuoteInvitation; use App\Models\RecurringInvoice; use App\Utils\Traits\MakesHash; @@ -57,9 +59,127 @@ class CompanyExport implements ShouldQueue set_time_limit(0); - $this->export_data['company'] = $this->company->makeHidden(['id','account_id'])->toArray(); - $this->export_data['design'] = $this->company->user_designs->makeHidden(['id'])->toArray(); + $this->export_data['clients'] = $this->company->clients->map(function ($client){ + + $client = $this->transformArrayOfKeys($client, ['id', 'company_id', 'user_id']); + + return $company; + + })->toArray(); + + $this->export_data['company'] = $this->company->map(function ($company){ + + $company = $this->transformArrayOfKeys($company, ['id', 'account_id']); + + return $company; + + })->toArray(); + + $this->export_data['company_gateways'] = $this->company->company_gateways->map(function ($company_gateway){ + + $company_gateway = $this->transformArrayOfKeys($company_gateway, ['company_id', 'user_id']); + + return $company_gateway; + + })->toArray(); + + $this->export_data['company_tokens'] = $this->company->tokens->map(function ($token){ + + $token = $this->transformArrayOfKeys($token, ['company_id', 'account_id', 'user_id']); + + return $token; + + })->toArray(); + + $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; + + })->toArray(); + + $this->export_data['company_users'] = $this->company->company_users->map(function ($company_user){ + + $company_user = $this->transformArrayOfKeys($company_user, ['company_id', 'account_id', 'user_id']); + + return $company_user; + + })->toArray(); + + $this->export_data['credits'] = $this->company->credits->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']); + + return $credit; + + })->toArray(); + + + $this->export_data['credit_invitations'] = CreditInvitation::where('company_id', $this->company_id)->withTrashed()->cursor()->map(function ($credit){ + + $credit = $this->transformArrayOfKeys($credit, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']); + + return $credit; + + })->toArray(); + + $this->export_data['designs'] = $this->company->user_designs->makeHidden(['id'])->toArray(); + + $this->export_data['documents'] = $this->company->documents->map(function ($document){ + + $document = $this->transformArrayOfKeys($document, ['user_id', 'assigned_user_id', 'company_id', 'project_id', 'vendor_id']); + + return $document; + + })->toArray(); + + $this->export_data['expense_categories'] = $this->company->expenses->map(function ($expense_category){ + + $expense_category = $this->transformArrayOfKeys($expense_category, ['user_id', 'company_id']); + + return $expense_category; + + })->toArray(); + + + $this->export_data['expenses'] = $this->company->expenses->map(function ($expense){ + + $expense = $this->transformBasicEntities($expense); + $expense = $this->transformArrayOfKeys($expense, ['vendor_id', 'invoice_id', 'client_id', 'category_id', 'recurring_expense_id','project_id']); + + return $expense; + + })->toArray(); + + $this->export_data['group_settings'] = $this->company->group_settings->map(function ($gs){ + + $gs = $this->transformArrayOfKeys($gs, ['user_id', 'company_id']); + + return $gs; + + })->toArray(); + + + $this->export_data['invoices'] = $this->company->invoices->map(function ($invoice){ + + $invoice = $this->transformBasicEntities($invoice); + $invoice = $this->transformArrayOfKeys($invoice, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']); + + return $invoice; + + })->toArray(); + + + $this->export_data['invoice_invitations'] = InvoiceInvitation::where('company_id', $this->company_id)->withTrashed()->cursor()->map(function ($invoice){ + + $invoice = $this->transformArrayOfKeys($invoice, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']); + + return $invoice; + + })->toArray(); $this->export_data['payment_terms'] = $this->company->user_payment_terms->map(function ($term){ @@ -69,6 +189,24 @@ class CompanyExport implements ShouldQueue })->makeHidden(['id'])->toArray(); + $this->export_data['paymentables'] = $this->company->payments()->with('paymentables')->cursor()->map(function ($paymentable){ + + $paymentable = $this->transformArrayOfKeys($paymentable, ['payment_id','paymentable_id']); + + return $paymentable; + + })->toArray(); + + $this->export_data['payments'] = $this->company->payments->map(function ($payment){ + + $payment = $this->transformBasicEntities($payment); + $payment = $this->transformArrayOfKeys($payment, ['client_id','project_id', 'vendor_id', 'client_contact_id', 'invitation_id', 'company_gateway_id']); + + return $project; + + })->toArray(); + + $this->export_data['projects'] = $this->company->projects->map(function ($project){ $project = $this->transformBasicEntities($project); From 2688f7472e69258b4cc76a9b6fd0f7a3953aa9de Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 13 May 2021 13:32:36 +1000 Subject: [PATCH 5/8] fixes for sofort cancellation --- app/Jobs/Company/CompanyExport.php | 98 +++++++++++++++++++++------- app/Models/Company.php | 11 ++++ app/PaymentDrivers/Stripe/SOFORT.php | 2 - 3 files changed, 87 insertions(+), 24 deletions(-) diff --git a/app/Jobs/Company/CompanyExport.php b/app/Jobs/Company/CompanyExport.php index 5069c5891e72..ed514acee466 100644 --- a/app/Jobs/Company/CompanyExport.php +++ b/app/Jobs/Company/CompanyExport.php @@ -17,6 +17,8 @@ use App\Models\CreditInvitation; use App\Models\InvoiceInvitation; use App\Models\QuoteInvitation; use App\Models\RecurringInvoice; +use App\Models\RecurringInvoiceInvitation; +use App\Models\VendorContact; use App\Utils\Traits\MakesHash; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -59,22 +61,74 @@ class CompanyExport implements ShouldQueue set_time_limit(0); + $this->export_data['app_version'] = config('ninja.app_version'); + + $this->export_data['activities'] = $this->company->all_activities->map(function ($activity){ + + $activity = $this->transformArrayOfKeys($activity, [ + 'user_id', + 'company_id', + 'client_id', + 'client_contact_id', + 'account_id', + 'project_id', + 'vendor_id', + 'payment_id', + 'invoice_id', + 'credit_id', + 'invitation_id', + 'task_id', + 'expense_id', + 'token_id', + 'quote_id', + 'subscription_id', + 'recurring_invoice_id' + ]); + + return $activity; + + })->toArray(); + + $this->export_data['backups'] = $this->company->all_activities()->with('backup')->cursor()->map(function ($activity){ + + $backup = $activity->backup; + $backup->activity_id = $this->encodePrimaryKey($backup->activity_id); + + return $backup; + + })->toArray(); + + $this->export_data['client_contacts'] = $this->company->client_contacts->map(function ($client_contact){ + + $client_contact = $this->transformArrayOfKeys($client_contact, ['id', 'company_id', 'user_id',' client_id']); + + return $client_contact; + + })->toArray(); + + + $this->export_data['client_gateway_tokens'] = $this->company->client_gateway_tokens->map(function ($client_gateway_token){ + + $client_gateway_token = $this->transformArrayOfKeys($client_gateway_token, ['id', 'company_id', 'client_id']); + + return $client_gateway_token; + + })->toArray(); + $this->export_data['clients'] = $this->company->clients->map(function ($client){ - $client = $this->transformArrayOfKeys($client, ['id', 'company_id', 'user_id']); + $client = $this->transformArrayOfKeys($client, ['id', 'company_id', 'user_id',' assigned_user_id', 'group_settings_id']); - return $company; + return $client; })->toArray(); - $this->export_data['company'] = $this->company->map(function ($company){ + $temp_co = $this->company; + $temp_co->id = $this->encodePrimaryKey($temp_co->id); + $temp_co->account_id = $this->encodePrimaryKey($temp_co->account_id); - $company = $this->transformArrayOfKeys($company, ['id', 'account_id']); - - return $company; - - })->toArray(); + $this->export_data['company'] = $temp_co->toArray(); $this->export_data['company_gateways'] = $this->company->company_gateways->map(function ($company_gateway){ @@ -118,7 +172,7 @@ class CompanyExport implements ShouldQueue })->toArray(); - $this->export_data['credit_invitations'] = CreditInvitation::where('company_id', $this->company_id)->withTrashed()->cursor()->map(function ($credit){ + $this->export_data['credit_invitations'] = CreditInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($credit){ $credit = $this->transformArrayOfKeys($credit, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']); @@ -173,7 +227,7 @@ class CompanyExport implements ShouldQueue })->toArray(); - $this->export_data['invoice_invitations'] = InvoiceInvitation::where('company_id', $this->company_id)->withTrashed()->cursor()->map(function ($invoice){ + $this->export_data['invoice_invitations'] = InvoiceInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($invoice){ $invoice = $this->transformArrayOfKeys($invoice, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']); @@ -226,7 +280,7 @@ class CompanyExport implements ShouldQueue })->toArray(); - $this->export_data['quote_invitations'] = QuoteInvitation::where('company_id', $this->company_id)->withTrashed()->cursor()->map(function ($quote){ + $this->export_data['quote_invitations'] = QuoteInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($quote){ $quote = $this->transformArrayOfKeys($quote, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']); @@ -238,13 +292,13 @@ class CompanyExport implements ShouldQueue $this->export_data['recurring_invoices'] = $this->company->recurring_invoices->map(function ($ri){ $ri = $this->transformBasicEntities($ri); - $ri = $this->transformArrayOfKeys($ri, [['client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']); + $ri = $this->transformArrayOfKeys($ri, ['client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']); return $ri; })->toArray(); - $this->export_data['recurring_invoice_invitations'] = RecurringInvoice::where('company_id', $this->company_id)->withTrashed()->cursor()->map(function ($ri){ + $this->export_data['recurring_invoice_invitations'] = RecurringInvoiceInvitation::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']); @@ -259,7 +313,7 @@ class CompanyExport implements ShouldQueue return $subscription; - })->makeHidden([])->toArray(); + })->toArray(); $this->export_data['system_logs'] = $this->company->system_logs->map(function ($log){ @@ -276,9 +330,9 @@ class CompanyExport implements ShouldQueue $task = $this->transformBasicEntities($task); $task = $this->transformArrayOfKeys(['client_id', 'invoice_id', 'project_id', 'status_id']); - return $task + return $task; - })->makeHidden([])->toArray(); + })->toArray(); $this->export_data['task_statuses'] = $this->company->task_statuses->map(function ($status){ @@ -288,14 +342,14 @@ class CompanyExport implements ShouldQueue return $status; - })->makeHidden([])->toArray(); + })->toArray(); $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); - return $rate + return $rate; })->makeHidden(['id'])->toArray(); @@ -312,17 +366,17 @@ class CompanyExport implements ShouldQueue return $this->transformBasicEntities($vendor); - })->makeHidden([])->toArray(); + })->toArray(); - $this->export_data['vendor_contacts'] = $this->company->vendor->contacts->map(function ($vendor){ + $this->export_data['vendor_contacts'] = VendorContact::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($vendor){ $vendor = $this->transformBasicEntities($vendor); $vendor->vendor_id = $this->encodePrimaryKey($vendor->vendor_id); return $vendor; - })->makeHidden([])->toArray(); + })->toArray(); $this->export_data['webhooks'] = $this->company->webhooks->map(function ($hook){ @@ -333,7 +387,7 @@ class CompanyExport implements ShouldQueue })->makeHidden(['id'])->toArray(); - + var_dump($this->export_data); } private function transformBasicEntities($model) diff --git a/app/Models/Company.php b/app/Models/Company.php index 82dfe0a9520f..47ea068fea24 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -150,6 +150,11 @@ class Company extends BaseModel return $this->belongsTo(Account::class); } + public function client_contacts() + { + return $this->hasMany(ClientContact::class)->withTrashed(); + } + public function users() { return $this->hasManyThrough(User::class, CompanyUser::class, 'company_id', 'id', 'id', 'user_id'); @@ -203,6 +208,12 @@ class Company extends BaseModel return $this->hasMany(Vendor::class)->withTrashed(); } + public function all_activities() + { + return $this->hasMany(Activity::class); + } + + public function activities() { return $this->hasMany(Activity::class)->orderBy('id', 'DESC')->take(300); diff --git a/app/PaymentDrivers/Stripe/SOFORT.php b/app/PaymentDrivers/Stripe/SOFORT.php index af96cdf1c693..77d6e63a57d8 100644 --- a/app/PaymentDrivers/Stripe/SOFORT.php +++ b/app/PaymentDrivers/Stripe/SOFORT.php @@ -102,8 +102,6 @@ class SOFORT { $server_response = $this->stripe->payment_hash->data; - PaymentFailureMailer::dispatch($this->stripe->client, $server_response->redirect_status, $this->stripe->client->company, $server_response->amount); - PaymentFailureMailer::dispatch( $this->stripe->client, $server_response, From 75cf46b77e91f5e25bc2b1e0ebfaf64a77725948 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 13 May 2021 13:57:18 +1000 Subject: [PATCH 6/8] tests for exporting data --- tests/Feature/Export/ExportCompanyTest.php | 49 ++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tests/Feature/Export/ExportCompanyTest.php diff --git a/tests/Feature/Export/ExportCompanyTest.php b/tests/Feature/Export/ExportCompanyTest.php new file mode 100644 index 000000000000..189b1b47ddb8 --- /dev/null +++ b/tests/Feature/Export/ExportCompanyTest.php @@ -0,0 +1,49 @@ +withoutMiddleware( + ThrottleRequests::class + ); + + // $this->faker = \Faker\Factory::create(); + + $this->makeTestData(); + + $this->withoutExceptionHandling(); + } + + public function testCompanyExport() + { + CompanyExport::dispatchNow($this->company); + } +} From e4fabfbf2dfad5f5507b73d8851e947ea20dc9b4 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 13 May 2021 16:01:12 +1000 Subject: [PATCH 7/8] Export data --- .../Auth/ForgotPasswordController.php | 4 +- app/Jobs/Company/CompanyExport.php | 72 +++++++++++++++++-- app/Libraries/MultiDB.php | 7 +- resources/lang/ca/texts.php | 1 + resources/lang/en/texts.php | 1 + routes/api.php | 2 + tests/Feature/Export/ExportCompanyTest.php | 2 +- 7 files changed, 76 insertions(+), 13 deletions(-) diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 4d81705ea3f6..10036a77bda7 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -104,9 +104,9 @@ class ForgotPasswordController extends Controller */ public function sendResetLinkEmail(Request $request) { - //MultiDB::userFindAndSetDb($request->input('email')); + MultiDB::userFindAndSetDb($request->input('email')); - $user = MultiDB::hasUser(['email' => $request->input('email')]); + // $user = MultiDB::hasUser(['email' => $request->input('email')]); $this->validateEmail($request); diff --git a/app/Jobs/Company/CompanyExport.php b/app/Jobs/Company/CompanyExport.php index ed514acee466..f9206969fd8b 100644 --- a/app/Jobs/Company/CompanyExport.php +++ b/app/Jobs/Company/CompanyExport.php @@ -11,13 +11,19 @@ namespace App\Jobs\Company; +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; use App\Models\Company; use App\Models\CreditInvitation; use App\Models\InvoiceInvitation; use App\Models\QuoteInvitation; use App\Models\RecurringInvoice; use App\Models\RecurringInvoiceInvitation; +use App\Models\User; use App\Models\VendorContact; use App\Utils\Traits\MakesHash; use Illuminate\Bus\Queueable; @@ -25,17 +31,22 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Storage; +use ZipStream\Option\Archive; +use ZipStream\ZipStream; class CompanyExport implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash; - protected $company; + public $company; private $export_format; private $export_data = []; + public $user; + /** * Create a new job instance. * @@ -43,9 +54,10 @@ class CompanyExport implements ShouldQueue * @param User $user * @param string $custom_token_name */ - public function __construct(Company $company, $export_format = 'json') + public function __construct(Company $company, User $user, $export_format = 'json') { $this->company = $company; + $this->user = $user; $this->export_format = $export_format; } @@ -87,11 +99,15 @@ class CompanyExport implements ShouldQueue return $activity; - })->toArray(); + })->makeHidden(['id'])->toArray(); $this->export_data['backups'] = $this->company->all_activities()->with('backup')->cursor()->map(function ($activity){ $backup = $activity->backup; + + if(!$backup) + return; + $backup->activity_id = $this->encodePrimaryKey($backup->activity_id); return $backup; @@ -309,7 +325,7 @@ class CompanyExport implements ShouldQueue $this->export_data['subscriptions'] = $this->company->subscriptions->map(function ($subscription){ $subscription = $this->transformBasicEntities($subscription); - $subscription->group_id = $this->encodePrimaryKey($group_id); + $subscription->group_id = $this->encodePrimaryKey($subscription->group_id); return $subscription; @@ -328,7 +344,7 @@ class CompanyExport implements ShouldQueue $this->export_data['tasks'] = $this->company->tasks->map(function ($task){ $task = $this->transformBasicEntities($task); - $task = $this->transformArrayOfKeys(['client_id', 'invoice_id', 'project_id', 'status_id']); + $task = $this->transformArrayOfKeys($task, ['client_id', 'invoice_id', 'project_id', 'status_id']); return $task; @@ -387,7 +403,9 @@ class CompanyExport implements ShouldQueue })->makeHidden(['id'])->toArray(); - var_dump($this->export_data); + //write to tmp and email to owner(); + + $this->zipAndSend(); } private function transformBasicEntities($model) @@ -408,4 +426,46 @@ class CompanyExport implements ShouldQueue } + private function zipAndSend() + { + nlog("zipping"); + + $tempStream = fopen('php://memory', 'w+'); + + $options = new Archive(); + $options->setOutputStream($tempStream); + + $file_name = date('Y-m-d').'_'.str_replace(' ', '_', $this->company->present()->name() . '_' . $this->company->company_key .'.zip'); + + $zip = new ZipStream($file_name, $options); + + $fp = tmpfile(); + fwrite($fp, json_encode($this->export_data)); + rewind($fp); + $zip->addFileFromStream('backup.json', $fp); + + $zip->finish(); + + $path = 'backups/'; + + nlog($path.$file_name); + + Storage::disk(config('filesystems.default'))->put($path.$file_name, $tempStream); + // fclose($fp); + + nlog(Storage::disk(config('filesystems.default'))->url($path.$file_name)); + + fclose($tempStream); + + $nmo = new NinjaMailerObject; + $nmo->mailable = new DownloadBackup(Storage::disk(config('filesystems.default'))->url($path.$file_name), $this->company); + $nmo->to_user = $this->user; + $nmo->settings = $this->company->settings; + $nmo->company = $this->company; + + NinjaMailerJob::dispatch($nmo); + + UnlinkFile::dispatch(config('filesystems.default'), $path.$file_name)->delay(now()->addHours(1)); + } + } diff --git a/app/Libraries/MultiDB.php b/app/Libraries/MultiDB.php index 92132a118be2..27673891e09f 100644 --- a/app/Libraries/MultiDB.php +++ b/app/Libraries/MultiDB.php @@ -129,13 +129,12 @@ class MultiDB } foreach (self::$dbs as $db) { + self::setDB($db); - $user = User::where($data)->withTrashed()->first(); - - if ($user) { + if ($user = User::where($data)->withTrashed()->first()) return $user; - } + } self::setDefaultDatabase(); diff --git a/resources/lang/ca/texts.php b/resources/lang/ca/texts.php index 5302d2431d11..efa11815049c 100644 --- a/resources/lang/ca/texts.php +++ b/resources/lang/ca/texts.php @@ -2861,6 +2861,7 @@ $LANG = [ 'my_invoices' => 'My Invoices', 'mobile_refresh_warning' => 'If you\'re using the mobile app you may need to do a full refresh.', 'enable_proposals_for_background' => 'To upload a background image :link to enable the proposals module.', + ]; diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 2a46a25013ba..7f1174d56bf1 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -4248,6 +4248,7 @@ $LANG = array( 'activity_104' => ':user restored recurring invoice :recurring_invoice', 'new_login_detected' => 'New login detected for your account.', 'new_login_description' => 'You recently logged in to your Invoice Ninja account from a new location or device:

IP: :ip
Time: :time
Email: :email', + 'download_backup_subject' => 'Your company backup is ready for download', ); return $LANG; diff --git a/routes/api.php b/routes/api.php index ed9029c41ff2..b46bd12f0d2e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -75,6 +75,8 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a Route::put('expenses/{expense}/upload', 'ExpenseController@upload'); Route::post('expenses/bulk', 'ExpenseController@bulk')->name('expenses.bulk'); + Route::post('export', 'ExportController@index')->name('export.index'); + Route::resource('expense_categories', 'ExpenseCategoryController'); // name = (expense_categories. index / create / show / update / destroy / edit Route::post('expense_categories/bulk', 'ExpenseCategoryController@bulk')->name('expense_categories.bulk'); diff --git a/tests/Feature/Export/ExportCompanyTest.php b/tests/Feature/Export/ExportCompanyTest.php index 189b1b47ddb8..bfdad43e3845 100644 --- a/tests/Feature/Export/ExportCompanyTest.php +++ b/tests/Feature/Export/ExportCompanyTest.php @@ -44,6 +44,6 @@ class ExportCompanyTest extends TestCase public function testCompanyExport() { - CompanyExport::dispatchNow($this->company); + CompanyExport::dispatchNow($this->company, $this->company->users->first()); } } From ef359501ba4d4cd72b07aa183be6338abfee0aea Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 13 May 2021 16:16:39 +1000 Subject: [PATCH 8/8] Export Controller --- app/Http/Controllers/ExportController.php | 64 +++++++++++++++++++ .../Requests/Export/StoreExportRequest.php | 39 +++++++++++ app/Mail/DownloadBackup.php | 41 ++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 app/Http/Controllers/ExportController.php create mode 100644 app/Http/Requests/Export/StoreExportRequest.php create mode 100644 app/Mail/DownloadBackup.php diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php new file mode 100644 index 000000000000..e2d99efa8e93 --- /dev/null +++ b/app/Http/Controllers/ExportController.php @@ -0,0 +1,64 @@ +user()->getCompany(), auth()->user()); + + return response()->json(['message' => 'Processing'], 200); + + } +} diff --git a/app/Http/Requests/Export/StoreExportRequest.php b/app/Http/Requests/Export/StoreExportRequest.php new file mode 100644 index 000000000000..96d322a53df6 --- /dev/null +++ b/app/Http/Requests/Export/StoreExportRequest.php @@ -0,0 +1,39 @@ +user()->isAdmin(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return []; + } +} diff --git a/app/Mail/DownloadBackup.php b/app/Mail/DownloadBackup.php new file mode 100644 index 000000000000..5b354a5db265 --- /dev/null +++ b/app/Mail/DownloadBackup.php @@ -0,0 +1,41 @@ +file_path = $file_path; + + $this->company = $company; + } + + /** + * Build the message. + */ + public function build() + { + return $this->from(config('mail.from.address'), config('mail.from.name')) + ->subject(ctrans('texts.download_backup_subject')) + ->markdown( + 'email.admin.download_files', + [ + 'url' => $this->file_path, + 'logo' => $this->company->present()->logo, + 'whitelabel' => $this->company->account->isPaid() ? true : false, + ] + ); + } +}