diff --git a/app/Http/Controllers/MigrationController.php b/app/Http/Controllers/MigrationController.php index bbc361c41321..964af9163952 100644 --- a/app/Http/Controllers/MigrationController.php +++ b/app/Http/Controllers/MigrationController.php @@ -14,7 +14,8 @@ namespace App\Http\Controllers; use App\Console\Commands\ImportMigrations; use App\DataMapper\CompanySettings; -use App\Jobs\Mail\MailRouter; +use App\Jobs\Mail\NinjaMailerJob; +use App\Jobs\Mail\NinjaMailerObject; use App\Jobs\Util\StartMigration; use App\Mail\ExistingMigration; use App\Models\Company; @@ -248,7 +249,13 @@ class MigrationController extends BaseController if ($checks['existing_company'] == true && $checks['force'] == false) { nlog('Migrating: Existing company without force. (CASE_01)'); - MailRouter::dispatch(new ExistingMigration(), $existing_company, $user); + $nmo = new NinjaMailerObject; + $nmo->mailable = new ExistingMigration(); + $nmo->company = $existing_company; + $nmo->settings = $existing_company->settings; + $nmo->to_user = $user; + + NinjaMailerJob::dispatch($nmo); return response()->json([ '_id' => Str::uuid(), diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 0f8cfd3c58d4..fb42d547e9a1 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -369,16 +369,21 @@ class UserController extends BaseController */ public function update(UpdateUserRequest $request, User $user) { + $old_company_user = $user->company_user; - $old_user = $user; + $old_user = json_encode($user); + $old_user_email = $user->getOriginal('email'); $new_email = $request->input('email'); + $new_user = $this->user_repo->save($request->all(), $user); + $new_user = $user->fresh(); - $user = $this->user_repo->save($request->all(), $user); - $user = $user->fresh(); - if ($old_user->email != $new_email) - UserEmailChanged::dispatch($new_user, $old_user, auth()->user()->company()); + nlog($old_user); + + if ($old_user_email != $new_email) + UserEmailChanged::dispatch($new_user, json_decode($old_user), auth()->user()->company()); + if( strcasecmp($old_company_user->permissions, $user->company_user->permissions) != 0 || diff --git a/app/Jobs/Import/CSVImport.php b/app/Jobs/Import/CSVImport.php index 12acb0169349..3e9df315bf6d 100644 --- a/app/Jobs/Import/CSVImport.php +++ b/app/Jobs/Import/CSVImport.php @@ -17,7 +17,8 @@ use App\Factory\PaymentFactory; use App\Http\Requests\Invoice\StoreInvoiceRequest; use App\Import\ImportException; use App\Import\Transformers\BaseTransformer; -use App\Jobs\Mail\MailRouter; +use App\Jobs\Mail\NinjaMailerJob; +use App\Jobs\Mail\NinjaMailerObject; use App\Libraries\MultiDB; use App\Mail\Import\ImportCompleted; use App\Models\Client; @@ -91,23 +92,12 @@ class CSVImport implements ShouldQueue { MultiDB::setDb( $this->company->db ); - $this->company->owner()->setCompany( $this->company ); Auth::login( $this->company->owner(), true ); + $this->company->owner()->setCompany( $this->company ); + $this->buildMaps(); - /** - * Execute the job. - * - * - * @return void - */ - public function handle() - { - nlog("starting import"); - - MultiDB::setDb($this->company->db); - nlog( "import " . $this->import_type ); foreach ( [ 'client', 'product', 'invoice', 'payment', 'vendor', 'expense' ] as $entityType ) { $csvData = $this->getCsvData( $entityType ); @@ -139,9 +129,14 @@ class CSVImport implements ShouldQueue { 'company' => $this->company, ]; - MailRouter::dispatch( new ImportCompleted( $data ), $this->company, auth()->user() ); - } + $nmo = new NinjaMailerObject; + $nmo->mailable = new ImportCompleted( $data ); + $nmo->company = $this->company; + $nmo->settings = $this->company->settings; + $nmo->to_user = $this->company->owner(); + NinjaMailerJob::dispatch($nmo); + } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// private function preTransformCsv( $csvData, $entityType ) { @@ -610,4 +605,4 @@ class CSVImport implements ShouldQueue { return $data; } -} +} \ No newline at end of file diff --git a/app/Jobs/Mail/BaseMailerJob.php b/app/Jobs/Mail/BaseMailerJob.php deleted file mode 100644 index c11abe32ccc2..000000000000 --- a/app/Jobs/Mail/BaseMailerJob.php +++ /dev/null @@ -1,120 +0,0 @@ -settings)); - - switch ($this->settings->email_sending_method) { - case 'default': - break; - case 'gmail': - $this->setGmailMailer(); - break; - default: - break; - } - } - - public function setGmailMailer() - { - $sending_user = $this->settings->gmail_sending_user_id; - - $user = User::find($this->decodePrimaryKey($sending_user)); - - $google = (new Google())->init(); - $google->getClient()->setAccessToken(json_encode($user->oauth_user_token)); - - if ($google->getClient()->isAccessTokenExpired()) { - $google->refreshToken($user); - } - - /* - * Now that our token is refreshed and valid we can boot the - * mail driver at runtime and also set the token which will persist - * just for this request. - */ - - // config(['mail.driver' => 'gmail']); - // config(['services.gmail.token' => $user->oauth_user_token->access_token]); - // config(['mail.from.address' => $user->email]); - // config(['mail.from.name' => $user->present()->name()]); - - //(new MailServiceProvider(app()))->register(); - - nlog("after registering mail service provider"); - nlog(config('services.gmail.token')); - } - - public function logMailError($errors, $recipient_object) - { - SystemLogger::dispatch( - $errors, - SystemLog::CATEGORY_MAIL, - SystemLog::EVENT_MAIL_SEND, - SystemLog::TYPE_FAILURE, - $recipient_object - ); - } - - public function failed($exception = null) - { - nlog('mailer job failed'); - nlog($exception->getMessage()); - - $job_failure = new EmailFailure(); - $job_failure->string_metric5 = get_parent_class($this); - $job_failure->string_metric6 = $exception->getMessage(); - - LightLogs::create($job_failure) - ->batch(); - } -} diff --git a/app/Jobs/Mail/MailRouter.php b/app/Jobs/Mail/MailRouter.php deleted file mode 100644 index c2673d4e050f..000000000000 --- a/app/Jobs/Mail/MailRouter.php +++ /dev/null @@ -1,83 +0,0 @@ -mailable = $mailable; - - $this->company = $company; - - $this->to_user = $to_user; - - $this->sending_method = $sending_method; - - if ($to_user instanceof ClientContact) { - $this->settings = $to_user->client->getMergedSettings(); - } else { - $this->settings = $this->company->settings; - } - } - - public function handle() - { - /*If we are migrating data we don't want to fire these notification*/ - if ($this->company->is_disabled) { - return true; - } - - MultiDB::setDb($this->company->db); - - //if we need to set an email driver do it now - $this->setMailDriver(); - - //send email - try { - Mail::to($this->to_user->email) - ->send($this->mailable); - } catch (\Exception $e) { - //$this->failed($e); - - if ($this->to_user instanceof ClientContact) { - $this->logMailError($e->getMessage(), $this->to_user->client); - } - } - } -} diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index 598c0cfcd0c9..a040556d386a 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -80,6 +80,7 @@ class NinjaMailerJob implements ShouldQueue } catch (\Exception $e) { nlog("error failed with {$e->getMessage()}"); + nlog($e); if($this->nmo->entity) $this->entityEmailFailed($e->getMessage()); @@ -120,7 +121,7 @@ class NinjaMailerJob implements ShouldQueue switch ($this->nmo->settings->email_sending_method) { case 'default': - config(['mail.driver' => config('mail.default')]); + //config(['mail.driver' => config('mail.default')]); break; case 'gmail': $this->setGmailMailer(); diff --git a/app/Jobs/Mail/PaymentFailureMailer.php b/app/Jobs/Mail/PaymentFailureMailer.php index 5bb1ef5dbfde..1fb0f000db5c 100644 --- a/app/Jobs/Mail/PaymentFailureMailer.php +++ b/app/Jobs/Mail/PaymentFailureMailer.php @@ -28,7 +28,7 @@ use Illuminate\Support\Facades\Mail; /*Multi Mailer implemented*/ -class PaymentFailureMailer extends BaseMailerJob implements ShouldQueue +class PaymentFailureMailer implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, UserNotifies; diff --git a/app/Jobs/User/UserEmailChanged.php b/app/Jobs/User/UserEmailChanged.php index b4021fe1cb6a..621287a492f9 100644 --- a/app/Jobs/User/UserEmailChanged.php +++ b/app/Jobs/User/UserEmailChanged.php @@ -44,7 +44,7 @@ class UserEmailChanged implements ShouldQueue * @param string $old_email * @param Company $company */ - public function __construct(User $new_user, User $old_user, Company $company) + public function __construct(User $new_user, $old_user, Company $company) { $this->new_user = $new_user; $this->old_user = $old_user; @@ -54,9 +54,10 @@ class UserEmailChanged implements ShouldQueue public function handle() { - if ($this->company->is_disabled) { + nlog("notifying user of email change"); + + if ($this->company->is_disabled) return true; - } //Set DB MultiDB::setDb($this->company->db); @@ -91,8 +92,8 @@ class UserEmailChanged implements ShouldQueue 'title' => ctrans('texts.email_address_changed'), 'message' => ctrans( 'texts.email_address_changed_message', - ['old_email' => $this->old_email, - 'new_email' => $this->new_email, + ['old_email' => $this->old_user->email, + 'new_email' => $this->new_user->email, ] ), 'url' => config('ninja.app_url'), diff --git a/app/Listeners/Credit/CreditEmailedNotification.php b/app/Listeners/Credit/CreditEmailedNotification.php index 610eda58dd45..dd7f3bd5e06f 100644 --- a/app/Listeners/Credit/CreditEmailedNotification.php +++ b/app/Listeners/Credit/CreditEmailedNotification.php @@ -52,7 +52,7 @@ class CreditEmailedNotification implements ShouldQueue foreach ($event->invitation->company->company_users as $company_user) { $user = $company_user->user; - $notification = new EntitySentNotification($event->invitation, 'credit'); + // $notification = new EntitySentNotification($event->invitation, 'credit'); $methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'credit', ['all_notifications', 'credit_sent']); @@ -66,9 +66,9 @@ class CreditEmailedNotification implements ShouldQueue $first_notification_sent = false; } - $notification->method = $methods; + // $notification->method = $methods; - $user->notify($notification); + // $user->notify($notification); } } } diff --git a/app/Listeners/Invoice/InvoiceEmailedNotification.php b/app/Listeners/Invoice/InvoiceEmailedNotification.php index d14da9e7ed3a..1bffd1d37649 100644 --- a/app/Listeners/Invoice/InvoiceEmailedNotification.php +++ b/app/Listeners/Invoice/InvoiceEmailedNotification.php @@ -57,7 +57,7 @@ class InvoiceEmailedNotification implements ShouldQueue $user = $company_user->user; /* This is only here to handle the alternate message channels - ie Slack */ - $notification = new EntitySentNotification($event->invitation, 'invoice'); + // $notification = new EntitySentNotification($event->invitation, 'invoice'); /* Returns an array of notification methods */ $methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'invoice', ['all_notifications', 'invoice_sent']); @@ -76,10 +76,10 @@ class InvoiceEmailedNotification implements ShouldQueue } /* Override the methods in the Notification Class */ - $notification->method = $methods; + // $notification->method = $methods; - /* Notify on the alternate channels */ - $user->notify($notification); + // Notify on the alternate channels + // $user->notify($notification); } } } diff --git a/app/Listeners/Invoice/InvoiceFailedEmailNotification.php b/app/Listeners/Invoice/InvoiceFailedEmailNotification.php index bf5e0aeb833c..aeda48fbfb62 100644 --- a/app/Listeners/Invoice/InvoiceFailedEmailNotification.php +++ b/app/Listeners/Invoice/InvoiceFailedEmailNotification.php @@ -20,7 +20,7 @@ use App\Notifications\Admin\EntitySentNotification; use App\Utils\Traits\Notifications\UserNotifies; use Illuminate\Contracts\Queue\ShouldQueue; -class InvoiceFailedEmailNotification implements ShouldQueue +class InvoiceFailedEmailNotification { use UserNotifies; @@ -54,7 +54,7 @@ class InvoiceFailedEmailNotification implements ShouldQueue foreach ($event->invitation->company->company_users as $company_user) { $user = $company_user->user; - $notification = new EntitySentNotification($event->invitation, 'invoice'); + // $notification = new EntitySentNotification($event->invitation, 'invoice'); $methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'invoice', ['all_notifications', 'invoice_sent']); @@ -68,9 +68,9 @@ class InvoiceFailedEmailNotification implements ShouldQueue $first_notification_sent = false; } - $notification->method = $methods; + // $notification->method = $methods; - $user->notify($notification); + // $user->notify($notification); } } } diff --git a/app/Listeners/Misc/InvitationViewedListener.php b/app/Listeners/Misc/InvitationViewedListener.php index 1cb7083cc255..953ec7d44ad5 100644 --- a/app/Listeners/Misc/InvitationViewedListener.php +++ b/app/Listeners/Misc/InvitationViewedListener.php @@ -47,7 +47,7 @@ class InvitationViewedListener implements ShouldQueue $entity_name = lcfirst(class_basename($event->entity)); $invitation = $event->invitation; - $notification = new EntityViewedNotification($invitation, $entity_name); + // $notification = new EntityViewedNotification($invitation, $entity_name); $nmo = new NinjaMailerObject; $nmo->mailable = new NinjaMailer( (new EntityViewedObject($invitation, $entity_name))->build() ); @@ -68,16 +68,16 @@ class InvitationViewedListener implements ShouldQueue } - $notification->method = $methods; + // $notification->method = $methods; - $company_user->user->notify($notification); + // $company_user->user->notify($notification); } - if (isset($invitation->company->slack_webhook_url)) { - $notification->method = ['slack']; + // if (isset($invitation->company->slack_webhook_url)) { + // $notification->method = ['slack']; // Notification::route('slack', $invitation->company->slack_webhook_url) // ->notify($notification); - } + // } } } diff --git a/app/Listeners/Payment/PaymentNotification.php b/app/Listeners/Payment/PaymentNotification.php index 7b5420c83df7..8503e6eee5f7 100644 --- a/app/Listeners/Payment/PaymentNotification.php +++ b/app/Listeners/Payment/PaymentNotification.php @@ -69,19 +69,19 @@ class PaymentNotification implements ShouldQueue NinjaMailerJob::dispatch($nmo); } - $notification = new NewPaymentNotification($payment, $payment->company); - $notification->method = $methods; + // $notification = new NewPaymentNotification($payment, $payment->company); + // $notification->method = $methods; - if ($user) { - $user->notify($notification); - } + // if ($user) { + // $user->notify($notification); + // } } /*Company Notifications*/ - if (isset($payment->company->slack_webhook_url)) { - Notification::route('slack', $payment->company->slack_webhook_url) - ->notify(new NewPaymentNotification($payment, $payment->company, true)); - } + // if (isset($payment->company->slack_webhook_url)) { + // Notification::route('slack', $payment->company->slack_webhook_url) + // ->notify(new NewPaymentNotification($payment, $payment->company, true)); + // } /*Google Analytics Track Revenue*/ if (isset($payment->company->google_analytics_key)) { diff --git a/app/Listeners/Quote/QuoteEmailedNotification.php b/app/Listeners/Quote/QuoteEmailedNotification.php index 62b88518d9f5..7cb0995f638d 100644 --- a/app/Listeners/Quote/QuoteEmailedNotification.php +++ b/app/Listeners/Quote/QuoteEmailedNotification.php @@ -53,7 +53,7 @@ class QuoteEmailedNotification implements ShouldQueue foreach ($event->invitation->company->company_users as $company_user) { $user = $company_user->user; - $notification = new EntitySentNotification($event->invitation, 'quote'); + // $notification = new EntitySentNotification($event->invitation, 'quote'); $methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'quote', ['all_notifications', 'quote_sent']); @@ -68,9 +68,9 @@ class QuoteEmailedNotification implements ShouldQueue $first_notification_sent = false; } - $notification->method = $methods; + // $notification->method = $methods; - $user->notify($notification); + // $user->notify($notification); } } } \ No newline at end of file diff --git a/app/Mail/Admin/EntityFailedSendObject.php b/app/Mail/Admin/EntityFailedSendObject.php index e1b74dc736f1..bc7d228fba48 100644 --- a/app/Mail/Admin/EntityFailedSendObject.php +++ b/app/Mail/Admin/EntityFailedSendObject.php @@ -131,6 +131,7 @@ class EntityFailedSendObject 'client' => $this->contact->present()->name(), 'invoice' => $this->entity->number, 'error' => $this->message, + 'contact' => $this->contact->present()->name(), ] ), 'url' => $this->invitation->getAdminLink(), diff --git a/app/Notifications/Admin/EntitySentNotification.php b/app/Notifications/Admin/EntitySentNotification.php index b499d8313699..617fab502ba6 100644 --- a/app/Notifications/Admin/EntitySentNotification.php +++ b/app/Notifications/Admin/EntitySentNotification.php @@ -22,10 +22,9 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; //@deprecated -class EntitySentNotification extends Notification implements ShouldQueue +class EntitySentNotification extends Notification { - //use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - + /** * Create a new notification instance. * diff --git a/app/Notifications/Admin/EntityViewedNotification.php b/app/Notifications/Admin/EntityViewedNotification.php index 5fbdad38de01..42493551b2e5 100644 --- a/app/Notifications/Admin/EntityViewedNotification.php +++ b/app/Notifications/Admin/EntityViewedNotification.php @@ -21,9 +21,8 @@ use Illuminate\Notifications\Notification; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -class EntityViewedNotification extends Notification implements ShouldQueue +class EntityViewedNotification extends Notification { - //use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; /** * Create a new notification instance. diff --git a/app/Notifications/Admin/NewPaymentNotification.php b/app/Notifications/Admin/NewPaymentNotification.php index 5becc897ab9e..61ad0c191205 100644 --- a/app/Notifications/Admin/NewPaymentNotification.php +++ b/app/Notifications/Admin/NewPaymentNotification.php @@ -21,9 +21,8 @@ use Illuminate\Notifications\Notification; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -class NewPaymentNotification extends Notification implements ShouldQueue +class NewPaymentNotification extends Notification { - // use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; /** * Create a new notification instance. diff --git a/tests/Feature/Import/ImportCsvTest.php b/tests/Feature/Import/ImportCsvTest.php index 5a5d20643bd6..651edfa59215 100644 --- a/tests/Feature/Import/ImportCsvTest.php +++ b/tests/Feature/Import/ImportCsvTest.php @@ -8,593 +8,344 @@ * * @license https://opensource.org/licenses/AAL */ +namespace Tests\Feature\Import; -namespace App\Jobs\Import; - -use App\Factory\ClientFactory; -use App\Factory\InvoiceFactory; -use App\Factory\PaymentFactory; -use App\Http\Requests\Invoice\StoreInvoiceRequest; -use App\Import\ImportException; -use App\Import\Transformers\BaseTransformer; -use App\Jobs\Mail\MailRouter; -use App\Libraries\MultiDB; -use App\Mail\Import\ImportCompleted; +use App\Jobs\Import\CSVImport; use App\Models\Client; -use App\Models\ClientContact; -use App\Models\Company; -use App\Models\Country; -use App\Models\Currency; -use App\Models\ExpenseCategory; +use App\Models\Expense; use App\Models\Invoice; -use App\Models\PaymentType; +use App\Models\Payment; use App\Models\Product; -use App\Models\Project; -use App\Models\TaxRate; -use App\Models\User; use App\Models\Vendor; -use App\Repositories\BaseRepository; -use App\Repositories\ClientRepository; -use App\Repositories\InvoiceRepository; -use App\Repositories\PaymentRepository; -use App\Utils\Traits\CleanLineItems; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Auth; +use App\Utils\Traits\MakesHash; +use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; use League\Csv\Reader; use League\Csv\Statement; -use Symfony\Component\HttpFoundation\ParameterBag; -use Symfony\Component\HttpFoundation\Request; +use Tests\MockAccountData; +use Tests\TestCase; -class CSVImport implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, CleanLineItems; +/** + * @test + * @covers App\Http\Controllers\ImportController + */ +class ImportCsvTest extends TestCase +{ + use MakesHash; + use MockAccountData; - public $invoice; + public function setUp() :void + { + parent::setUp(); - public $company; + $this->withoutMiddleware( + ThrottleRequests::class + ); - public $hash; + // $this->faker = \Faker\Factory::create(); - public $import_type; + $this->makeTestData(); - public $skip_header; + $this->withoutExceptionHandling(); + } - public $column_map; + public function testCsvRead() + { + $csv = file_get_contents(base_path().'/tests/Feature/Import/invoice.csv'); - public $import_array; + $this->assertTrue(is_array($this->getCsvData($csv))); + } - public $error_array = []; - - public $maps; - - public function __construct( array $request, Company $company ) { - $this->company = $company; - $this->hash = $request['hash']; - $this->import_type = $request['import_type']; - $this->skip_header = $request['skip_header'] ?? null; - $this->column_map = $request['column_map'] ?? null; - } - - /** - * Execute the job. - * - * - * @return void - */ - public function handle() { - - MultiDB::setDb( $this->company->db ); - - $this->company->owner()->setCompany( $this->company ); - Auth::login( $this->company->owner(), true ); - - $this->buildMaps(); - - nlog( "import " . $this->import_type ); - foreach ( [ 'client', 'product', 'invoice', 'payment', 'vendor', 'expense' ] as $entityType ) { - $csvData = $this->getCsvData( $entityType ); - - if ( ! empty( $csvData ) ) { - $importFunction = "import" . Str::plural( Str::title( $entityType ) ); - $preTransformFunction = "preTransform" . Str::title( $this->import_type ); - - if ( method_exists( $this, $preTransformFunction ) ) { - $csvData = $this->$preTransformFunction( $csvData, $entityType ); - } - - if ( empty( $csvData ) ) { - continue; - } - - if ( method_exists( $this, $importFunction ) ) { - // If there's an entity-specific import function, use that. - $this->$importFunction( $csvData ); - } else { - // Otherwise, use the generic import function. - $this->importEntities( $csvData, $entityType ); - } - } - } + public function testClientCsvImport() + { + $csv = file_get_contents(base_path().'/tests/Feature/Import/clients.csv'); + $hash = Str::random(32); + $column_map = [ + 1 => 'client.balance', + 2 => 'client.paid_to_date', + 0 => 'client.name', + 19 => 'client.currency_id', + 20 => 'client.public_notes', + 21 => 'client.private_notes', + 22 => 'contact.first_name', + 23 => 'contact.last_name', + ]; $data = [ - 'errors' => $this->error_array, - 'company' => $this->company, + 'hash' => $hash, + 'column_map' => [ 'client' => $column_map ], + 'skip_header' => true, + 'import_type' => 'csv', ]; - MailRouter::dispatch( new ImportCompleted( $data ), $this->company, auth()->user() ); + $pre_import = Client::count(); + + Cache::put( $hash . '-client', base64_encode( $csv ), 360 ); + + CSVImport::dispatchNow( $data, $this->company ); + + $this->assertGreaterThan( $pre_import, Client::count() ); } - ////////////////////////////////////////////////////////////////////////////////////////////////////////////// - private function preTransformCsv( $csvData, $entityType ) { - if ( empty( $this->column_map[ $entityType ] ) ) { - return false; - } - - if ( $this->skip_header ) { - array_shift( $csvData ); - } - - //sort the array by key - $keys = $this->column_map[ $entityType ]; - ksort( $keys ); - - $csvData = array_map( function ( $row ) use ( $keys ) { - return array_combine( $keys, array_intersect_key( $row, $keys ) ); - }, $csvData ); - - if ( $entityType === 'invoice' ) { - $csvData = $this->groupInvoices( $csvData, 'invoice.number' ); - } - - return $csvData; - } - - private function preTransformFreshbooks( $csvData, $entityType ) { - $csvData = $this->mapCSVHeaderToKeys( $csvData ); - - if ( $entityType === 'invoice' ) { - $csvData = $this->groupInvoices( $csvData, 'Invoice #' ); - } - - return $csvData; - } - - private function preTransformInvoicely( $csvData, $entityType ) { - $csvData = $this->mapCSVHeaderToKeys( $csvData ); - - return $csvData; - } - - private function preTransformInvoice2go( $csvData, $entityType ) { - $csvData = $this->mapCSVHeaderToKeys( $csvData ); - - return $csvData; - } - - private function preTransformZoho( $csvData, $entityType ) { - $csvData = $this->mapCSVHeaderToKeys( $csvData ); - - if ( $entityType === 'invoice' ) { - $csvData = $this->groupInvoices( $csvData, 'Invoice Number' ); - } - - return $csvData; - } - - private function preTransformWaveaccounting( $csvData, $entityType ) { - $csvData = $this->mapCSVHeaderToKeys( $csvData ); - - if ( $entityType === 'invoice' ) { - $csvData = $this->groupInvoices( $csvData, 'Invoice Number' ); - } - - return $csvData; - } - - private function groupInvoices( $csvData, $key ) { - // Group by invoice. - $grouped = []; - - foreach ( $csvData as $line_item ) { - if ( empty( $line_item[ $key ] ) ) { - $this->error_array['invoice'][] = [ 'invoice' => $line_item, 'error' => 'No invoice number' ]; - } else { - $grouped[ $line_item[ $key ] ][] = $line_item; - } - } - - return $grouped; - } - - private function mapCSVHeaderToKeys( $csvData ) { - $keys = array_shift( $csvData ); - - return array_map( function ( $values ) use ( $keys ) { - return array_combine( $keys, $values ); - }, $csvData ); - } - - ////////////////////////////////////////////////////////////////////////////////////////////////////////////// - private function importInvoices( $invoices ) { - $invoice_transformer = $this->getTransformer( 'invoice' ); - - /** @var PaymentRepository $payment_repository */ - $payment_repository = app()->make( PaymentRepository::class ); - $payment_repository->import_mode = true; - - /** @var ClientRepository $client_repository */ - $client_repository = app()->make( ClientRepository::class ); - $client_repository->import_mode = true; - - $invoice_repository = new InvoiceRepository(); - $invoice_repository->import_mode = true; - - foreach ( $invoices as $raw_invoice ) { - try { - $invoice_data = $invoice_transformer->transform( $raw_invoice ); - - $invoice_data['line_items'] = $this->cleanItems( $invoice_data['line_items'] ?? [] ); - - - // If we don't have a client ID, but we do have client data, go ahead and create the client. - if ( empty( $invoice_data['client_id'] ) && ! empty( $invoice_data['client'] ) ) { - $client_data = $invoice_data['client']; - $client_data['user_id'] = $this->getUserIDForRecord( $invoice_data ); - - $client_repository->save( - $client_data, - $client = ClientFactory::create( $this->company->id, $client_data['user_id'] ) - ); - $invoice_data['client_id'] = $client->id; - unset( $invoice_data['client'] ); - } - - $validator = Validator::make( $invoice_data, ( new StoreInvoiceRequest() )->rules() ); - if ( $validator->fails() ) { - $this->error_array['invoice'][] = - [ 'invoice' => $invoice_data, 'error' => $validator->errors()->all() ]; - } else { - $invoice = InvoiceFactory::create( $this->company->id, $this->getUserIDForRecord( $invoice_data ) ); - if ( ! empty( $invoice_data['status_id'] ) ) { - $invoice->status_id = $invoice_data['status_id']; - } - $invoice_repository->save( $invoice_data, $invoice ); - $this->addInvoiceToMaps( $invoice ); - - // If we're doing a generic CSV import, only import payment data if we're not importing a payment CSV. - // If we're doing a platform-specific import, trust the platform to only return payment info if there's not a separate payment CSV. - if ( $this->import_type !== 'csv' || empty( $this->column_map['payment'] ) ) { - // Check for payment columns - if ( ! empty( $invoice_data['payments'] ) ) { - foreach ( $invoice_data['payments'] as $payment_data ) { - $payment_data['user_id'] = $invoice->user_id; - $payment_data['client_id'] = $invoice->client_id; - $payment_data['invoices'] = [ - [ - 'invoice_id' => $invoice->id, - 'amount' => $payment_data['amount'] ?? null, - ], - ]; - - $payment_repository->save( - $payment_data, - PaymentFactory::create( $this->company->id, $invoice->user_id, $invoice->client_id ) - ); - } - } - } - - $this->actionInvoiceStatus( $invoice, $invoice_data, $invoice_repository ); - } - } catch ( \Exception $ex ) { - if ( $ex instanceof ImportException ) { - $message = $ex->getMessage(); - } else { - report( $ex ); - $message = 'Unknown error'; - } - - $this->error_array['invoice'][] = [ 'invoice' => $raw_invoice, 'error' => $message ]; - } - } - } - - private function actionInvoiceStatus( $invoice, $invoice_data, $invoice_repository ) { - if ( ! empty( $invoice_data['archived'] ) ) { - $invoice_repository->archive( $invoice ); - $invoice->fresh(); - } - - if ( ! empty( $invoice_data['viewed'] ) ) { - $invoice = $invoice->service()->markViewed()->save(); - } - - if ( $invoice->status_id === Invoice::STATUS_SENT ) { - $invoice = $invoice->service()->markSent()->save(); - } - - if ( $invoice->status_id <= Invoice::STATUS_SENT && $invoice->amount > 0 ) { - if ( $invoice->balance < $invoice->amount ) { - $invoice->status_id = Invoice::STATUS_PARTIAL; - $invoice->save(); - } elseif ( $invoice->balance <= 0 ) { - $invoice->status_id = Invoice::STATUS_PAID; - $invoice->save(); - } - } - - - return $invoice; - } - - private function importEntities( $records, $entity_type ) { - $entity_type = Str::slug( $entity_type, '_' ); - $formatted_entity_type = Str::title( $entity_type ); - - $request_name = "\\App\\Http\\Requests\\${formatted_entity_type}\\Store${formatted_entity_type}Request"; - $repository_name = '\\App\\Repositories\\' . $formatted_entity_type . 'Repository'; - $factoryName = '\\App\\Factory\\' . $formatted_entity_type . 'Factory'; - - /** @var BaseRepository $repository */ - $repository = app()->make( $repository_name ); - $repository->import_mode = true; - - $transformer = $this->getTransformer( $entity_type ); - - foreach ( $records as $record ) { - try { - $entity = $transformer->transform( $record ); - - /** @var \App\Http\Requests\Request $request */ - $request = new $request_name(); - - // Pass entity data to request so it can be validated - $request->query = $request->request = new ParameterBag( $entity ); - $validator = Validator::make( $entity, $request->rules() ); - - if ( $validator->fails() ) { - $this->error_array[ $entity_type ][] = - [ $entity_type => $record, 'error' => $validator->errors()->all() ]; - } else { - $entity = - $repository->save( - array_diff_key( $entity, [ 'user_id' => false ] ), - $factoryName::create( $this->company->id, $this->getUserIDForRecord( $entity ) ) ); - - $entity->save(); - if ( method_exists( $this, 'add' . $formatted_entity_type . 'ToMaps' ) ) { - $this->{'add' . $formatted_entity_type . 'ToMaps'}( $entity ); - } - } - } catch ( \Exception $ex ) { - if ( $ex instanceof ImportException ) { - $message = $ex->getMessage(); - } else { - report( $ex ); - $message = 'Unknown error'; - } - - $this->error_array[ $entity_type ][] = [ $entity_type => $record, 'error' => $message ]; - } - } - } - - /** - * @param $entity_type - * - * @return BaseTransformer - */ - private function getTransformer( $entity_type ) { - $formatted_entity_type = Str::title( $entity_type ); - $formatted_import_type = Str::title( $this->import_type ); - $transformer_name = - '\\App\\Import\\Transformers\\' . $formatted_import_type . '\\' . $formatted_entity_type . 'Transformer'; - - return new $transformer_name( $this->maps ); - } - - ////////////////////////////////////////////////////////////////////////////////////////////////////////////// - private function buildMaps() { - $this->maps = [ - 'company' => $this->company, - 'client' => [], - 'contact' => [], - 'invoice' => [], - 'invoice_client' => [], - 'product' => [], - 'countries' => [], - 'countries2' => [], - 'currencies' => [], - 'client_ids' => [], - 'invoice_ids' => [], - 'vendors' => [], - 'expense_categories' => [], - 'payment_types' => [], - 'tax_rates' => [], - 'tax_names' => [], + public function testInvoiceCsvImport() + { + /*Need to import clients first*/ + $csv = file_get_contents(base_path().'/tests/Feature/Import/clients.csv'); + $hash = Str::random(32); + $column_map = [ + 1 => 'client.balance', + 2 => 'client.paid_to_date', + 0 => 'client.name', + 19 => 'client.currency_id', + 20 => 'client.public_notes', + 21 => 'client.private_notes', + 22 => 'contact.first_name', + 23 => 'contact.last_name', ]; - $clients = Client::scope()->get(); - foreach ( $clients as $client ) { - $this->addClientToMaps( $client ); - } + $data = [ + 'hash' => $hash, + 'column_map' => [ 'client' => $column_map ], + 'skip_header' => true, + 'import_type' => 'csv', + ]; - $contacts = ClientContact::scope()->get(); - foreach ( $contacts as $contact ) { - $this->addContactToMaps( $contact ); - } + Cache::put( $hash . '-client', base64_encode( $csv ), 360 ); - $invoices = Invoice::scope()->get(); - foreach ( $invoices as $invoice ) { - $this->addInvoiceToMaps( $invoice ); - } + CSVImport::dispatchNow( $data, $this->company ); - $products = Product::scope()->get(); - foreach ( $products as $product ) { - $this->addProductToMaps( $product ); - } + /*Now import invoices*/ + $csv = file_get_contents(base_path().'/tests/Feature/Import/invoice.csv'); + $hash = Str::random(32); - $projects = Project::scope()->get(); - foreach ( $projects as $project ) { - $this->addProjectToMaps( $project ); - } + $column_map = [ + 1 => 'client.email', + 3 => 'payment.amount', + 5 => 'invoice.po_number', + 8 => 'invoice.due_date', + 9 => 'item.discount', + 11 => 'invoice.partial_due_date', + 12 => 'invoice.public_notes', + 13 => 'invoice.private_notes', + 0 => 'client.name', + 2 => 'invoice.number', + 7 => 'invoice.date', + 14 => 'item.product_key', + 15 => 'item.notes', + 16 => 'item.cost', + 17 => 'item.quantity', + ]; - $countries = Country::all(); - foreach ( $countries as $country ) { - $this->maps['countries'][ strtolower( $country->name ) ] = $country->id; - $this->maps['countries2'][ strtolower( $country->iso_3166_2 ) ] = $country->id; - } + $data = [ + 'hash' => $hash, + 'column_map' => [ 'invoice' => $column_map ], + 'skip_header' => true, + 'import_type' => 'csv', + ]; - $currencies = Currency::all(); - foreach ( $currencies as $currency ) { - $this->maps['currencies'][ strtolower( $currency->code ) ] = $currency->id; - } + $pre_import = Invoice::count(); - $payment_types = PaymentType::all(); - foreach ( $payment_types as $payment_type ) { - $this->maps['payment_types'][ strtolower( $payment_type->name ) ] = $payment_type->id; - } + Cache::put( $hash . '-invoice', base64_encode( $csv ), 360 ); - $vendors = Vendor::scope()->get(); - foreach ( $vendors as $vendor ) { - $this->addVendorToMaps( $vendor ); - } + CSVImport::dispatchNow( $data, $this->company ); - $expenseCaegories = ExpenseCategory::scope()->get(); - foreach ( $expenseCaegories as $category ) { - $this->addExpenseCategoryToMaps( $category ); - } - - $taxRates = TaxRate::scope()->get(); - foreach ( $taxRates as $taxRate ) { - $name = trim( strtolower( $taxRate->name ) ); - $this->maps['tax_rates'][ $name ] = $taxRate->rate; - $this->maps['tax_names'][ $name ] = $taxRate->name; - } + $this->assertGreaterThan( $pre_import, Invoice::count() ); } - /** - * @param Invoice $invoice - */ - private function addInvoiceToMaps( Invoice $invoice ) { - if ( $number = strtolower( trim( $invoice->number ) ) ) { - $this->maps['invoices'][ $number ] = $invoice; - $this->maps['invoice'][ $number ] = $invoice->id; - $this->maps['invoice_client'][ $number ] = $invoice->client_id; - $this->maps['invoice_ids'][ $invoice->public_id ] = $invoice->id; - } + public function testVendorCsvImport() { + $csv = file_get_contents( base_path() . '/tests/Feature/Import/vendors.csv' ); + $hash = Str::random( 32 ); + $column_map = [ + 0 => 'vendor.name', + 19 => 'vendor.currency_id', + 20 => 'vendor.public_notes', + 21 => 'vendor.private_notes', + 22 => 'vendor.first_name', + 23 => 'vendor.last_name', + ]; + + $data = [ + 'hash' => $hash, + 'column_map' => [ 'vendor' => $column_map ], + 'skip_header' => true, + 'import_type' => 'csv', + ]; + + $pre_import = Vendor::count(); + + Cache::put( $hash . '-vendor', base64_encode( $csv ), 360 ); + + CSVImport::dispatchNow( $data, $this->company ); + + $this->assertGreaterThan( $pre_import, Vendor::count() ); } - /** - * @param Client $client - */ - private function addClientToMaps( Client $client ) { - if ( $name = strtolower( trim( $client->name ) ) ) { - $this->maps['client'][ $name ] = $client->id; - $this->maps['client_ids'][ $client->public_id ] = $client->id; - } - if ( $client->contacts->count() ) { - $contact = $client->contacts[0]; - if ( $email = strtolower( trim( $contact->email ) ) ) { - $this->maps['client'][ $email ] = $client->id; - } - if ( $name = strtolower( trim( $contact->first_name . ' ' . $contact->last_name ) ) ) { - $this->maps['client'][ $name ] = $client->id; - } - $this->maps['client_ids'][ $client->public_id ] = $client->id; - } + public function testProductCsvImport() { + $csv = file_get_contents( base_path() . '/tests/Feature/Import/products.csv' ); + $hash = Str::random( 32 ); + + $column_map = [ + 2 => 'product.notes', + 3 => 'product.cost', + ]; + + $data = [ + 'hash' => $hash, + 'column_map' => [ 'product' => $column_map ], + 'skip_header' => true, + 'import_type' => 'csv', + ]; + + $pre_import = Product::count(); + + Cache::put( $hash . '-product', base64_encode( $csv ), 360 ); + + CSVImport::dispatchNow( $data, $this->company ); + + $this->assertGreaterThan( $pre_import, Product::count() ); } - /** - * @param ClientContact $contact - */ - private function addContactToMaps( ClientContact $contact ) { - if ( $key = strtolower( trim( $contact->email ) ) ) { - $this->maps['contact'][ $key ] = $contact; - } + public function testExpenseCsvImport() { + $csv = file_get_contents( base_path() . '/tests/Feature/Import/expenses.csv' ); + $hash = Str::random( 32 ); + + $column_map = [ + 2 => 'expense.public_notes', + 3 => 'expense.amount', + ]; + + $data = [ + 'hash' => $hash, + 'column_map' => [ 'expense' => $column_map ], + 'skip_header' => true, + 'import_type' => 'csv', + ]; + + $pre_import = Expense::count(); + + Cache::put( $hash . '-expense', base64_encode( $csv ), 360 ); + + CSVImport::dispatchNow( $data, $this->company ); + + $this->assertGreaterThan( $pre_import, Expense::count() ); } - /** - * @param Product $product - */ - private function addProductToMaps( Product $product ) { - if ( $key = strtolower( trim( $product->product_key ) ) ) { - $this->maps['product'][ $key ] = $product; - } + public function testPaymentCsvImport() { + +/*Need to import clients first*/ + $csv = file_get_contents(base_path().'/tests/Feature/Import/clients.csv'); + $hash = Str::random(32); + $column_map = [ + 1 => 'client.balance', + 2 => 'client.paid_to_date', + 0 => 'client.name', + 19 => 'client.currency_id', + 20 => 'client.public_notes', + 21 => 'client.private_notes', + 22 => 'contact.first_name', + 23 => 'contact.last_name', + ]; + + $data = [ + 'hash' => $hash, + 'column_map' => [ 'client' => $column_map ], + 'skip_header' => true, + 'import_type' => 'csv', + ]; + + Cache::put( $hash . '-client', base64_encode( $csv ), 360 ); + + CSVImport::dispatchNow( $data, $this->company ); + + /*Now import invoices*/ + $csv = file_get_contents(base_path().'/tests/Feature/Import/invoice.csv'); + $hash = Str::random(32); + + $column_map = [ + 1 => 'client.email', + 3 => 'payment.amount', + 5 => 'invoice.po_number', + 8 => 'invoice.due_date', + 9 => 'item.discount', + 11 => 'invoice.partial_due_date', + 12 => 'invoice.public_notes', + 13 => 'invoice.private_notes', + 0 => 'client.name', + 2 => 'invoice.number', + 7 => 'invoice.date', + 14 => 'item.product_key', + 15 => 'item.notes', + 16 => 'item.cost', + 17 => 'item.quantity', + ]; + + $data = [ + 'hash' => $hash, + 'column_map' => [ 'invoice' => $column_map ], + 'skip_header' => true, + 'import_type' => 'csv', + ]; + + $pre_import = Invoice::count(); + + Cache::put( $hash . '-invoice', base64_encode( $csv ), 360 ); + + CSVImport::dispatchNow( $data, $this->company ); + + + /* Test Now import payments*/ + + $csv = file_get_contents( base_path() . '/tests/Feature/Import/payments.csv' ); + $hash = Str::random( 32 ); + + $column_map = [ + 0 => 'payment.client_id', + 1 => 'payment.invoice_number', + 2 => 'payment.amount', + 3 => 'payment.date', + ]; + + $data = [ + 'hash' => $hash, + 'column_map' => [ 'payment' => $column_map ], + 'skip_header' => true, + 'import_type' => 'csv', + ]; + + $pre_import = Payment::count(); + + Cache::put( $hash . '-payment', base64_encode( $csv ), 360 ); + + CSVImport::dispatchNow( $data, $this->company ); + + $this->assertGreaterThan( $pre_import, Payment::count() ); } - /** - * @param Project $project - */ - private function addProjectToMaps( Project $project ) { - if ( $key = strtolower( trim( $project->name ) ) ) { - $this->maps['project'][ $key ] = $project; - } - } + private function getCsvData($csvfile) + { + if (! ini_get('auto_detect_line_endings')) { + ini_set('auto_detect_line_endings', '1'); + } - private function addVendorToMaps( Vendor $vendor ) { - $this->maps['vendor'][ strtolower( $vendor->name ) ] = $vendor->id; - } + $csv = Reader::createFromString($csvfile); + $stmt = new Statement(); + $data = iterator_to_array($stmt->process($csv)); - private function addExpenseCategoryToMaps( ExpenseCategory $category ) { - if ( $name = strtolower( $category->name ) ) { - $this->maps['expense_category'][ $name ] = $category->id; - } - } + if (count($data) > 0) { + $headers = $data[0]; + // Remove Invoice Ninja headers + if (count($headers) && count($data) > 4) { + $firstCell = $headers[0]; + if (strstr($firstCell, config('ninja.app_name'))) { + array_shift($data); // Invoice Ninja... + array_shift($data); // + array_shift($data); // Enitty Type Header + } + } + } - private function getUserIDForRecord( $record ) { - if ( ! empty( $record['user_id'] ) ) { - return $this->findUser( $record['user_id'] ); - } else { - return $this->company->owner()->id; - } - } - - private function findUser( $user_hash ) { - $user = User::where( 'company_id', $this->company->id ) - ->where( \DB::raw( 'CONCAT_WS(" ", first_name, last_name)' ), 'like', '%' . $user_hash . '%' ) - ->first(); - - if ( $user ) { - return $user->id; - } else { - return $this->company->owner()->id; - } - } - - private function getCsvData( $entityType ) { - $base64_encoded_csv = Cache::get( $this->hash . '-' . $entityType ); - if ( empty( $base64_encoded_csv ) ) { - return null; - } - - $csv = base64_decode( $base64_encoded_csv ); - $csv = Reader::createFromString( $csv ); - - $stmt = new Statement(); - $data = iterator_to_array( $stmt->process( $csv ) ); - - if ( count( $data ) > 0 ) { - $headers = $data[0]; - - // Remove Invoice Ninja headers - if ( count( $headers ) && count( $data ) > 4 && $this->import_type === 'csv' ) { - $firstCell = $headers[0]; - if ( strstr( $firstCell, config( 'ninja.app_name' ) ) ) { - array_shift( $data ); // Invoice Ninja... - array_shift( $data ); // - array_shift( $data ); // Enitty Type Header - } - } - } - - return $data; - } + return $data; + } } \ No newline at end of file