mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
commit
98ad55aa8d
@ -14,7 +14,8 @@ namespace App\Http\Controllers;
|
|||||||
|
|
||||||
use App\Console\Commands\ImportMigrations;
|
use App\Console\Commands\ImportMigrations;
|
||||||
use App\DataMapper\CompanySettings;
|
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\Jobs\Util\StartMigration;
|
||||||
use App\Mail\ExistingMigration;
|
use App\Mail\ExistingMigration;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
@ -248,7 +249,13 @@ class MigrationController extends BaseController
|
|||||||
if ($checks['existing_company'] == true && $checks['force'] == false) {
|
if ($checks['existing_company'] == true && $checks['force'] == false) {
|
||||||
nlog('Migrating: Existing company without force. (CASE_01)');
|
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([
|
return response()->json([
|
||||||
'_id' => Str::uuid(),
|
'_id' => Str::uuid(),
|
||||||
|
@ -369,16 +369,21 @@ class UserController extends BaseController
|
|||||||
*/
|
*/
|
||||||
public function update(UpdateUserRequest $request, User $user)
|
public function update(UpdateUserRequest $request, User $user)
|
||||||
{
|
{
|
||||||
|
|
||||||
$old_company_user = $user->company_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_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)
|
nlog($old_user);
|
||||||
UserEmailChanged::dispatch($new_user, $old_user, auth()->user()->company());
|
|
||||||
|
if ($old_user_email != $new_email)
|
||||||
|
UserEmailChanged::dispatch($new_user, json_decode($old_user), auth()->user()->company());
|
||||||
|
|
||||||
|
|
||||||
if(
|
if(
|
||||||
strcasecmp($old_company_user->permissions, $user->company_user->permissions) != 0 ||
|
strcasecmp($old_company_user->permissions, $user->company_user->permissions) != 0 ||
|
||||||
|
@ -17,7 +17,8 @@ use App\Factory\PaymentFactory;
|
|||||||
use App\Http\Requests\Invoice\StoreInvoiceRequest;
|
use App\Http\Requests\Invoice\StoreInvoiceRequest;
|
||||||
use App\Import\ImportException;
|
use App\Import\ImportException;
|
||||||
use App\Import\Transformers\BaseTransformer;
|
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\Libraries\MultiDB;
|
||||||
use App\Mail\Import\ImportCompleted;
|
use App\Mail\Import\ImportCompleted;
|
||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
@ -91,23 +92,12 @@ class CSVImport implements ShouldQueue {
|
|||||||
|
|
||||||
MultiDB::setDb( $this->company->db );
|
MultiDB::setDb( $this->company->db );
|
||||||
|
|
||||||
$this->company->owner()->setCompany( $this->company );
|
|
||||||
Auth::login( $this->company->owner(), true );
|
Auth::login( $this->company->owner(), true );
|
||||||
|
|
||||||
|
$this->company->owner()->setCompany( $this->company );
|
||||||
|
|
||||||
$this->buildMaps();
|
$this->buildMaps();
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the job.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
nlog("starting import");
|
|
||||||
|
|
||||||
MultiDB::setDb($this->company->db);
|
|
||||||
|
|
||||||
nlog( "import " . $this->import_type );
|
nlog( "import " . $this->import_type );
|
||||||
foreach ( [ 'client', 'product', 'invoice', 'payment', 'vendor', 'expense' ] as $entityType ) {
|
foreach ( [ 'client', 'product', 'invoice', 'payment', 'vendor', 'expense' ] as $entityType ) {
|
||||||
$csvData = $this->getCsvData( $entityType );
|
$csvData = $this->getCsvData( $entityType );
|
||||||
@ -139,9 +129,14 @@ class CSVImport implements ShouldQueue {
|
|||||||
'company' => $this->company,
|
'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 ) {
|
private function preTransformCsv( $csvData, $entityType ) {
|
||||||
@ -610,4 +605,4 @@ class CSVImport implements ShouldQueue {
|
|||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,120 +0,0 @@
|
|||||||
<?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\Mail;
|
|
||||||
|
|
||||||
use App\DataMapper\Analytics\EmailFailure;
|
|
||||||
use App\Jobs\Util\SystemLogger;
|
|
||||||
use App\Libraries\Google\Google;
|
|
||||||
use App\Models\SystemLog;
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Providers\MailServiceProvider;
|
|
||||||
use App\Utils\Ninja;
|
|
||||||
use App\Utils\Traits\MakesHash;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
use Illuminate\Support\Facades\App;
|
|
||||||
use Illuminate\Support\Facades\Config;
|
|
||||||
use Illuminate\Support\Facades\Lang;
|
|
||||||
use Turbo124\Beacon\Facades\LightLogs;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Multi Mailer implemented
|
|
||||||
@Deprecated 14/02/2021
|
|
||||||
*/
|
|
||||||
|
|
||||||
class BaseMailerJob implements ShouldQueue
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
|
|
||||||
|
|
||||||
public $tries = 5; //number of retries
|
|
||||||
|
|
||||||
public $backoff = 5; //seconds to wait until retry
|
|
||||||
|
|
||||||
public $deleteWhenMissingModels = true;
|
|
||||||
|
|
||||||
public function setMailDriver()
|
|
||||||
{
|
|
||||||
/* Singletons need to be rebooted each time just in case our Locale is changing*/
|
|
||||||
App::forgetInstance('translator');
|
|
||||||
App::forgetInstance('mail.manager'); //singletons must be destroyed!
|
|
||||||
|
|
||||||
/* Inject custom translations if any exist */
|
|
||||||
Lang::replace(Ninja::transformTranslations($this->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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
<?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\Mail;
|
|
||||||
|
|
||||||
use App\Libraries\MultiDB;
|
|
||||||
use App\Models\ClientContact;
|
|
||||||
use App\Models\Company;
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Mail\Mailable;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
use Illuminate\Support\Facades\Mail;
|
|
||||||
|
|
||||||
/*Multi Mailer Router implemented*/
|
|
||||||
|
|
||||||
class MailRouter extends BaseMailerJob implements ShouldQueue
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
public $mailable;
|
|
||||||
|
|
||||||
public $company;
|
|
||||||
|
|
||||||
public $to_user; //User or ClientContact
|
|
||||||
|
|
||||||
public $sending_method; //not sure if we even need this
|
|
||||||
|
|
||||||
public $settings;
|
|
||||||
|
|
||||||
public function __construct(Mailable $mailable, Company $company, $to_user, $sending_method = null)
|
|
||||||
{
|
|
||||||
$this->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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -80,6 +80,7 @@ class NinjaMailerJob implements ShouldQueue
|
|||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|
||||||
nlog("error failed with {$e->getMessage()}");
|
nlog("error failed with {$e->getMessage()}");
|
||||||
|
nlog($e);
|
||||||
|
|
||||||
if($this->nmo->entity)
|
if($this->nmo->entity)
|
||||||
$this->entityEmailFailed($e->getMessage());
|
$this->entityEmailFailed($e->getMessage());
|
||||||
@ -120,7 +121,7 @@ class NinjaMailerJob implements ShouldQueue
|
|||||||
|
|
||||||
switch ($this->nmo->settings->email_sending_method) {
|
switch ($this->nmo->settings->email_sending_method) {
|
||||||
case 'default':
|
case 'default':
|
||||||
config(['mail.driver' => config('mail.default')]);
|
//config(['mail.driver' => config('mail.default')]);
|
||||||
break;
|
break;
|
||||||
case 'gmail':
|
case 'gmail':
|
||||||
$this->setGmailMailer();
|
$this->setGmailMailer();
|
||||||
|
@ -28,7 +28,7 @@ use Illuminate\Support\Facades\Mail;
|
|||||||
|
|
||||||
/*Multi Mailer implemented*/
|
/*Multi Mailer implemented*/
|
||||||
|
|
||||||
class PaymentFailureMailer extends BaseMailerJob implements ShouldQueue
|
class PaymentFailureMailer implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, UserNotifies;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, UserNotifies;
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ class UserEmailChanged implements ShouldQueue
|
|||||||
* @param string $old_email
|
* @param string $old_email
|
||||||
* @param Company $company
|
* @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->new_user = $new_user;
|
||||||
$this->old_user = $old_user;
|
$this->old_user = $old_user;
|
||||||
@ -54,9 +54,10 @@ class UserEmailChanged implements ShouldQueue
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if ($this->company->is_disabled) {
|
nlog("notifying user of email change");
|
||||||
|
|
||||||
|
if ($this->company->is_disabled)
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
//Set DB
|
//Set DB
|
||||||
MultiDB::setDb($this->company->db);
|
MultiDB::setDb($this->company->db);
|
||||||
@ -91,8 +92,8 @@ class UserEmailChanged implements ShouldQueue
|
|||||||
'title' => ctrans('texts.email_address_changed'),
|
'title' => ctrans('texts.email_address_changed'),
|
||||||
'message' => ctrans(
|
'message' => ctrans(
|
||||||
'texts.email_address_changed_message',
|
'texts.email_address_changed_message',
|
||||||
['old_email' => $this->old_email,
|
['old_email' => $this->old_user->email,
|
||||||
'new_email' => $this->new_email,
|
'new_email' => $this->new_user->email,
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'url' => config('ninja.app_url'),
|
'url' => config('ninja.app_url'),
|
||||||
|
@ -52,7 +52,7 @@ class CreditEmailedNotification implements ShouldQueue
|
|||||||
foreach ($event->invitation->company->company_users as $company_user) {
|
foreach ($event->invitation->company->company_users as $company_user) {
|
||||||
$user = $company_user->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']);
|
$methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'credit', ['all_notifications', 'credit_sent']);
|
||||||
|
|
||||||
@ -66,9 +66,9 @@ class CreditEmailedNotification implements ShouldQueue
|
|||||||
$first_notification_sent = false;
|
$first_notification_sent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$notification->method = $methods;
|
// $notification->method = $methods;
|
||||||
|
|
||||||
$user->notify($notification);
|
// $user->notify($notification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ class InvoiceEmailedNotification implements ShouldQueue
|
|||||||
$user = $company_user->user;
|
$user = $company_user->user;
|
||||||
|
|
||||||
/* This is only here to handle the alternate message channels - ie Slack */
|
/* 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 */
|
/* Returns an array of notification methods */
|
||||||
$methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'invoice', ['all_notifications', 'invoice_sent']);
|
$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 */
|
/* Override the methods in the Notification Class */
|
||||||
$notification->method = $methods;
|
// $notification->method = $methods;
|
||||||
|
|
||||||
/* Notify on the alternate channels */
|
// Notify on the alternate channels
|
||||||
$user->notify($notification);
|
// $user->notify($notification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ use App\Notifications\Admin\EntitySentNotification;
|
|||||||
use App\Utils\Traits\Notifications\UserNotifies;
|
use App\Utils\Traits\Notifications\UserNotifies;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
|
||||||
class InvoiceFailedEmailNotification implements ShouldQueue
|
class InvoiceFailedEmailNotification
|
||||||
{
|
{
|
||||||
use UserNotifies;
|
use UserNotifies;
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ class InvoiceFailedEmailNotification implements ShouldQueue
|
|||||||
foreach ($event->invitation->company->company_users as $company_user) {
|
foreach ($event->invitation->company->company_users as $company_user) {
|
||||||
$user = $company_user->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']);
|
$methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'invoice', ['all_notifications', 'invoice_sent']);
|
||||||
|
|
||||||
@ -68,9 +68,9 @@ class InvoiceFailedEmailNotification implements ShouldQueue
|
|||||||
$first_notification_sent = false;
|
$first_notification_sent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$notification->method = $methods;
|
// $notification->method = $methods;
|
||||||
|
|
||||||
$user->notify($notification);
|
// $user->notify($notification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ class InvitationViewedListener implements ShouldQueue
|
|||||||
$entity_name = lcfirst(class_basename($event->entity));
|
$entity_name = lcfirst(class_basename($event->entity));
|
||||||
$invitation = $event->invitation;
|
$invitation = $event->invitation;
|
||||||
|
|
||||||
$notification = new EntityViewedNotification($invitation, $entity_name);
|
// $notification = new EntityViewedNotification($invitation, $entity_name);
|
||||||
|
|
||||||
$nmo = new NinjaMailerObject;
|
$nmo = new NinjaMailerObject;
|
||||||
$nmo->mailable = new NinjaMailer( (new EntityViewedObject($invitation, $entity_name))->build() );
|
$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)) {
|
// if (isset($invitation->company->slack_webhook_url)) {
|
||||||
$notification->method = ['slack'];
|
// $notification->method = ['slack'];
|
||||||
|
|
||||||
// Notification::route('slack', $invitation->company->slack_webhook_url)
|
// Notification::route('slack', $invitation->company->slack_webhook_url)
|
||||||
// ->notify($notification);
|
// ->notify($notification);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,19 +69,19 @@ class PaymentNotification implements ShouldQueue
|
|||||||
NinjaMailerJob::dispatch($nmo);
|
NinjaMailerJob::dispatch($nmo);
|
||||||
}
|
}
|
||||||
|
|
||||||
$notification = new NewPaymentNotification($payment, $payment->company);
|
// $notification = new NewPaymentNotification($payment, $payment->company);
|
||||||
$notification->method = $methods;
|
// $notification->method = $methods;
|
||||||
|
|
||||||
if ($user) {
|
// if ($user) {
|
||||||
$user->notify($notification);
|
// $user->notify($notification);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/*Company Notifications*/
|
/*Company Notifications*/
|
||||||
if (isset($payment->company->slack_webhook_url)) {
|
// if (isset($payment->company->slack_webhook_url)) {
|
||||||
Notification::route('slack', $payment->company->slack_webhook_url)
|
// Notification::route('slack', $payment->company->slack_webhook_url)
|
||||||
->notify(new NewPaymentNotification($payment, $payment->company, true));
|
// ->notify(new NewPaymentNotification($payment, $payment->company, true));
|
||||||
}
|
// }
|
||||||
|
|
||||||
/*Google Analytics Track Revenue*/
|
/*Google Analytics Track Revenue*/
|
||||||
if (isset($payment->company->google_analytics_key)) {
|
if (isset($payment->company->google_analytics_key)) {
|
||||||
|
@ -53,7 +53,7 @@ class QuoteEmailedNotification implements ShouldQueue
|
|||||||
foreach ($event->invitation->company->company_users as $company_user) {
|
foreach ($event->invitation->company->company_users as $company_user) {
|
||||||
$user = $company_user->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']);
|
$methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'quote', ['all_notifications', 'quote_sent']);
|
||||||
|
|
||||||
@ -68,9 +68,9 @@ class QuoteEmailedNotification implements ShouldQueue
|
|||||||
$first_notification_sent = false;
|
$first_notification_sent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$notification->method = $methods;
|
// $notification->method = $methods;
|
||||||
|
|
||||||
$user->notify($notification);
|
// $user->notify($notification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -131,6 +131,7 @@ class EntityFailedSendObject
|
|||||||
'client' => $this->contact->present()->name(),
|
'client' => $this->contact->present()->name(),
|
||||||
'invoice' => $this->entity->number,
|
'invoice' => $this->entity->number,
|
||||||
'error' => $this->message,
|
'error' => $this->message,
|
||||||
|
'contact' => $this->contact->present()->name(),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
'url' => $this->invitation->getAdminLink(),
|
'url' => $this->invitation->getAdminLink(),
|
||||||
|
@ -22,10 +22,9 @@ use Illuminate\Queue\InteractsWithQueue;
|
|||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
//@deprecated
|
//@deprecated
|
||||||
class EntitySentNotification extends Notification implements ShouldQueue
|
class EntitySentNotification extends Notification
|
||||||
{
|
{
|
||||||
//use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new notification instance.
|
* Create a new notification instance.
|
||||||
*
|
*
|
||||||
|
@ -21,9 +21,8 @@ use Illuminate\Notifications\Notification;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
class EntityViewedNotification extends Notification implements ShouldQueue
|
class EntityViewedNotification extends Notification
|
||||||
{
|
{
|
||||||
//use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new notification instance.
|
* Create a new notification instance.
|
||||||
|
@ -21,9 +21,8 @@ use Illuminate\Notifications\Notification;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
class NewPaymentNotification extends Notification implements ShouldQueue
|
class NewPaymentNotification extends Notification
|
||||||
{
|
{
|
||||||
// use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new notification instance.
|
* Create a new notification instance.
|
||||||
|
@ -8,593 +8,344 @@
|
|||||||
*
|
*
|
||||||
* @license https://opensource.org/licenses/AAL
|
* @license https://opensource.org/licenses/AAL
|
||||||
*/
|
*/
|
||||||
|
namespace Tests\Feature\Import;
|
||||||
|
|
||||||
namespace App\Jobs\Import;
|
use App\Jobs\Import\CSVImport;
|
||||||
|
|
||||||
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\Models\Client;
|
use App\Models\Client;
|
||||||
use App\Models\ClientContact;
|
use App\Models\Expense;
|
||||||
use App\Models\Company;
|
|
||||||
use App\Models\Country;
|
|
||||||
use App\Models\Currency;
|
|
||||||
use App\Models\ExpenseCategory;
|
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Models\PaymentType;
|
use App\Models\Payment;
|
||||||
use App\Models\Product;
|
use App\Models\Product;
|
||||||
use App\Models\Project;
|
|
||||||
use App\Models\TaxRate;
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Models\Vendor;
|
use App\Models\Vendor;
|
||||||
use App\Repositories\BaseRepository;
|
use App\Utils\Traits\MakesHash;
|
||||||
use App\Repositories\ClientRepository;
|
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||||
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 Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Validator;
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use League\Csv\Reader;
|
use League\Csv\Reader;
|
||||||
use League\Csv\Statement;
|
use League\Csv\Statement;
|
||||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
use Tests\MockAccountData;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
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 function testClientCsvImport()
|
||||||
|
{
|
||||||
public $maps;
|
$csv = file_get_contents(base_path().'/tests/Feature/Import/clients.csv');
|
||||||
|
$hash = Str::random(32);
|
||||||
public function __construct( array $request, Company $company ) {
|
$column_map = [
|
||||||
$this->company = $company;
|
1 => 'client.balance',
|
||||||
$this->hash = $request['hash'];
|
2 => 'client.paid_to_date',
|
||||||
$this->import_type = $request['import_type'];
|
0 => 'client.name',
|
||||||
$this->skip_header = $request['skip_header'] ?? null;
|
19 => 'client.currency_id',
|
||||||
$this->column_map = $request['column_map'] ?? null;
|
20 => 'client.public_notes',
|
||||||
}
|
21 => 'client.private_notes',
|
||||||
|
22 => 'contact.first_name',
|
||||||
/**
|
23 => 'contact.last_name',
|
||||||
* 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 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'errors' => $this->error_array,
|
'hash' => $hash,
|
||||||
'company' => $this->company,
|
'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() );
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
public function testInvoiceCsvImport()
|
||||||
private function preTransformCsv( $csvData, $entityType ) {
|
{
|
||||||
if ( empty( $this->column_map[ $entityType ] ) ) {
|
/*Need to import clients first*/
|
||||||
return false;
|
$csv = file_get_contents(base_path().'/tests/Feature/Import/clients.csv');
|
||||||
}
|
$hash = Str::random(32);
|
||||||
|
$column_map = [
|
||||||
if ( $this->skip_header ) {
|
1 => 'client.balance',
|
||||||
array_shift( $csvData );
|
2 => 'client.paid_to_date',
|
||||||
}
|
0 => 'client.name',
|
||||||
|
19 => 'client.currency_id',
|
||||||
//sort the array by key
|
20 => 'client.public_notes',
|
||||||
$keys = $this->column_map[ $entityType ];
|
21 => 'client.private_notes',
|
||||||
ksort( $keys );
|
22 => 'contact.first_name',
|
||||||
|
23 => 'contact.last_name',
|
||||||
$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' => [],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$clients = Client::scope()->get();
|
$data = [
|
||||||
foreach ( $clients as $client ) {
|
'hash' => $hash,
|
||||||
$this->addClientToMaps( $client );
|
'column_map' => [ 'client' => $column_map ],
|
||||||
}
|
'skip_header' => true,
|
||||||
|
'import_type' => 'csv',
|
||||||
|
];
|
||||||
|
|
||||||
$contacts = ClientContact::scope()->get();
|
Cache::put( $hash . '-client', base64_encode( $csv ), 360 );
|
||||||
foreach ( $contacts as $contact ) {
|
|
||||||
$this->addContactToMaps( $contact );
|
|
||||||
}
|
|
||||||
|
|
||||||
$invoices = Invoice::scope()->get();
|
CSVImport::dispatchNow( $data, $this->company );
|
||||||
foreach ( $invoices as $invoice ) {
|
|
||||||
$this->addInvoiceToMaps( $invoice );
|
|
||||||
}
|
|
||||||
|
|
||||||
$products = Product::scope()->get();
|
/*Now import invoices*/
|
||||||
foreach ( $products as $product ) {
|
$csv = file_get_contents(base_path().'/tests/Feature/Import/invoice.csv');
|
||||||
$this->addProductToMaps( $product );
|
$hash = Str::random(32);
|
||||||
}
|
|
||||||
|
|
||||||
$projects = Project::scope()->get();
|
$column_map = [
|
||||||
foreach ( $projects as $project ) {
|
1 => 'client.email',
|
||||||
$this->addProjectToMaps( $project );
|
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();
|
$data = [
|
||||||
foreach ( $countries as $country ) {
|
'hash' => $hash,
|
||||||
$this->maps['countries'][ strtolower( $country->name ) ] = $country->id;
|
'column_map' => [ 'invoice' => $column_map ],
|
||||||
$this->maps['countries2'][ strtolower( $country->iso_3166_2 ) ] = $country->id;
|
'skip_header' => true,
|
||||||
}
|
'import_type' => 'csv',
|
||||||
|
];
|
||||||
|
|
||||||
$currencies = Currency::all();
|
$pre_import = Invoice::count();
|
||||||
foreach ( $currencies as $currency ) {
|
|
||||||
$this->maps['currencies'][ strtolower( $currency->code ) ] = $currency->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
$payment_types = PaymentType::all();
|
Cache::put( $hash . '-invoice', base64_encode( $csv ), 360 );
|
||||||
foreach ( $payment_types as $payment_type ) {
|
|
||||||
$this->maps['payment_types'][ strtolower( $payment_type->name ) ] = $payment_type->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
$vendors = Vendor::scope()->get();
|
CSVImport::dispatchNow( $data, $this->company );
|
||||||
foreach ( $vendors as $vendor ) {
|
|
||||||
$this->addVendorToMaps( $vendor );
|
|
||||||
}
|
|
||||||
|
|
||||||
$expenseCaegories = ExpenseCategory::scope()->get();
|
$this->assertGreaterThan( $pre_import, Invoice::count() );
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function testVendorCsvImport() {
|
||||||
* @param Invoice $invoice
|
$csv = file_get_contents( base_path() . '/tests/Feature/Import/vendors.csv' );
|
||||||
*/
|
$hash = Str::random( 32 );
|
||||||
private function addInvoiceToMaps( Invoice $invoice ) {
|
$column_map = [
|
||||||
if ( $number = strtolower( trim( $invoice->number ) ) ) {
|
0 => 'vendor.name',
|
||||||
$this->maps['invoices'][ $number ] = $invoice;
|
19 => 'vendor.currency_id',
|
||||||
$this->maps['invoice'][ $number ] = $invoice->id;
|
20 => 'vendor.public_notes',
|
||||||
$this->maps['invoice_client'][ $number ] = $invoice->client_id;
|
21 => 'vendor.private_notes',
|
||||||
$this->maps['invoice_ids'][ $invoice->public_id ] = $invoice->id;
|
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() );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function testProductCsvImport() {
|
||||||
* @param Client $client
|
$csv = file_get_contents( base_path() . '/tests/Feature/Import/products.csv' );
|
||||||
*/
|
$hash = Str::random( 32 );
|
||||||
private function addClientToMaps( Client $client ) {
|
|
||||||
if ( $name = strtolower( trim( $client->name ) ) ) {
|
$column_map = [
|
||||||
$this->maps['client'][ $name ] = $client->id;
|
2 => 'product.notes',
|
||||||
$this->maps['client_ids'][ $client->public_id ] = $client->id;
|
3 => 'product.cost',
|
||||||
}
|
];
|
||||||
if ( $client->contacts->count() ) {
|
|
||||||
$contact = $client->contacts[0];
|
$data = [
|
||||||
if ( $email = strtolower( trim( $contact->email ) ) ) {
|
'hash' => $hash,
|
||||||
$this->maps['client'][ $email ] = $client->id;
|
'column_map' => [ 'product' => $column_map ],
|
||||||
}
|
'skip_header' => true,
|
||||||
if ( $name = strtolower( trim( $contact->first_name . ' ' . $contact->last_name ) ) ) {
|
'import_type' => 'csv',
|
||||||
$this->maps['client'][ $name ] = $client->id;
|
];
|
||||||
}
|
|
||||||
$this->maps['client_ids'][ $client->public_id ] = $client->id;
|
$pre_import = Product::count();
|
||||||
}
|
|
||||||
|
Cache::put( $hash . '-product', base64_encode( $csv ), 360 );
|
||||||
|
|
||||||
|
CSVImport::dispatchNow( $data, $this->company );
|
||||||
|
|
||||||
|
$this->assertGreaterThan( $pre_import, Product::count() );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function testExpenseCsvImport() {
|
||||||
* @param ClientContact $contact
|
$csv = file_get_contents( base_path() . '/tests/Feature/Import/expenses.csv' );
|
||||||
*/
|
$hash = Str::random( 32 );
|
||||||
private function addContactToMaps( ClientContact $contact ) {
|
|
||||||
if ( $key = strtolower( trim( $contact->email ) ) ) {
|
$column_map = [
|
||||||
$this->maps['contact'][ $key ] = $contact;
|
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() );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function testPaymentCsvImport() {
|
||||||
* @param Product $product
|
|
||||||
*/
|
/*Need to import clients first*/
|
||||||
private function addProductToMaps( Product $product ) {
|
$csv = file_get_contents(base_path().'/tests/Feature/Import/clients.csv');
|
||||||
if ( $key = strtolower( trim( $product->product_key ) ) ) {
|
$hash = Str::random(32);
|
||||||
$this->maps['product'][ $key ] = $product;
|
$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() );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function getCsvData($csvfile)
|
||||||
* @param Project $project
|
{
|
||||||
*/
|
if (! ini_get('auto_detect_line_endings')) {
|
||||||
private function addProjectToMaps( Project $project ) {
|
ini_set('auto_detect_line_endings', '1');
|
||||||
if ( $key = strtolower( trim( $project->name ) ) ) {
|
}
|
||||||
$this->maps['project'][ $key ] = $project;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addVendorToMaps( Vendor $vendor ) {
|
$csv = Reader::createFromString($csvfile);
|
||||||
$this->maps['vendor'][ strtolower( $vendor->name ) ] = $vendor->id;
|
$stmt = new Statement();
|
||||||
}
|
$data = iterator_to_array($stmt->process($csv));
|
||||||
|
|
||||||
private function addExpenseCategoryToMaps( ExpenseCategory $category ) {
|
if (count($data) > 0) {
|
||||||
if ( $name = strtolower( $category->name ) ) {
|
$headers = $data[0];
|
||||||
$this->maps['expense_category'][ $name ] = $category->id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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); // <blank line>
|
||||||
|
array_shift($data); // Enitty Type Header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function getUserIDForRecord( $record ) {
|
return $data;
|
||||||
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 ); // <blank line>
|
|
||||||
array_shift( $data ); // Enitty Type Header
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user