diff --git a/app/Events/Misc/InvitationWasViewed.php b/app/Events/Misc/InvitationWasViewed.php
new file mode 100644
index 000000000000..0f5a5463affe
--- /dev/null
+++ b/app/Events/Misc/InvitationWasViewed.php
@@ -0,0 +1,40 @@
+entity = $entity;
+ $this->invitation = $invitation;
+ }
+}
diff --git a/app/Helpers/Email/InvoiceEmail.php b/app/Helpers/Email/InvoiceEmail.php
index 8e603168be73..21f5c2434ff0 100644
--- a/app/Helpers/Email/InvoiceEmail.php
+++ b/app/Helpers/Email/InvoiceEmail.php
@@ -11,6 +11,7 @@ namespace App\Helpers\Email;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
+use App\Utils\Number;
class InvoiceEmail extends EmailBuilder
{
@@ -30,7 +31,12 @@ class InvoiceEmail extends EmailBuilder
/* Use default translations if a custom message has not been set*/
if (iconv_strlen($body_template) == 0) {
$body_template = trans('texts.invoice_message',
- ['invoice' => $invoice->number, 'company' => $invoice->company->present()->name()], null,
+ [
+ 'invoice' => $invoice->number,
+ 'company' => $invoice->company->present()->name(),
+ 'amount' => Number::formatMoney($invoice->balance, $invoice->client),
+ ],
+ null,
$invoice->client->locale());
}
diff --git a/app/Http/Controllers/ClientPortal/InvitationController.php b/app/Http/Controllers/ClientPortal/InvitationController.php
index 431e04e135c0..8ed79a833257 100644
--- a/app/Http/Controllers/ClientPortal/InvitationController.php
+++ b/app/Http/Controllers/ClientPortal/InvitationController.php
@@ -41,8 +41,14 @@ class InvitationController extends Controller
} else {
auth()->guard('contact')->login($invitation->contact, false);
}
-
- $invitation->markViewed();
+
+ if(!request()->has('is_admin')){
+
+ $invitation->markViewed();
+
+ event(new InvitationWasViewed($entity, $invitation));
+
+ }
return redirect()->route('client.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->{$key})]);
} else {
diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php
index 815a63977689..cd1c47375a23 100644
--- a/app/Http/Controllers/InvoiceController.php
+++ b/app/Http/Controllers/InvoiceController.php
@@ -12,6 +12,7 @@
namespace App\Http\Controllers;
use App\Events\Invoice\InvoiceWasCreated;
+use App\Events\Invoice\InvoiceWasEmailed;
use App\Events\Invoice\InvoiceWasUpdated;
use App\Factory\CloneInvoiceFactory;
use App\Factory\CloneInvoiceToQuoteFactory;
@@ -675,6 +676,9 @@ class InvoiceController extends BaseController {
}
break;
case 'email':
+
+ $this->reminder_template = $invoice->calculateTemplate();
+
$invoice->invitations->each(function ($invitation) use($invoice){
$email_builder = (new InvoiceEmail())->build($invitation, $this->reminder_template);
@@ -683,6 +687,11 @@ class InvoiceController extends BaseController {
});
+ if($invoice->invitations->count() > 0){
+ \Log::error("more than one invitation to send");
+ event(new InvoiceWasEmailed($invoice->invitations->first()));
+ }
+
if (!$bulk) {
return response()->json(['message' => 'email sent'], 200);
}
diff --git a/app/Http/Requests/Client/StoreClientRequest.php b/app/Http/Requests/Client/StoreClientRequest.php
index 43cf27f17797..6353b58998a5 100644
--- a/app/Http/Requests/Client/StoreClientRequest.php
+++ b/app/Http/Requests/Client/StoreClientRequest.php
@@ -94,7 +94,7 @@ class StoreClientRequest extends Request
{
$group_settings = GroupSetting::find($input['group_settings_id']);
- if($group_settings && property_exists($group_settings, 'currency_id') && is_int($group_settings->currency_id))
+ if($group_settings && property_exists($group_settings->settings, 'currency_id') && is_int($group_settings->settings->currency_id))
$input['settings']->currency_id = $group_settings->currency_id;
else
$input['settings']->currency_id = auth()->user()->company()->settings->currency_id;
diff --git a/app/Http/Requests/User/StoreUserRequest.php b/app/Http/Requests/User/StoreUserRequest.php
index b8594cdd1dbc..bce91953b56c 100644
--- a/app/Http/Requests/User/StoreUserRequest.php
+++ b/app/Http/Requests/User/StoreUserRequest.php
@@ -74,7 +74,7 @@ class StoreUserRequest extends Request
$this->replace($input);
}
-
+ //@todo make sure the user links back to the account ID for this company!!!!!!
public function fetchUser() :User
{
$user = MultiDB::hasUser(['email' => $this->input('email')]);
diff --git a/app/Listeners/Invoice/InvoiceEmailActivity.php b/app/Listeners/Invoice/InvoiceEmailActivity.php
index 8aa9db0ea578..f0ca3d408676 100644
--- a/app/Listeners/Invoice/InvoiceEmailActivity.php
+++ b/app/Listeners/Invoice/InvoiceEmailActivity.php
@@ -46,9 +46,9 @@ class InvoiceEmailActivity implements ShouldQueue
$fields->invoice_id = $event->invitation->invoice->id;
$fields->user_id = $event->invitation->invoice->user_id;
$fields->company_id = $event->invitation->invoice->company_id;
- $fields->contact_id = $event->invitation->invoice->client_contact_id;
+ $fields->client_contact_id = $event->invitation->invoice->client_contact_id;
$fields->activity_type_id = Activity::EMAIL_INVOICE;
- $this->activity_repo->save($fields, $event->invoice);
+ $this->activity_repo->save($fields, $event->invitation->invoice);
}
}
diff --git a/app/Listeners/Invoice/InvoiceEmailedNotification.php b/app/Listeners/Invoice/InvoiceEmailedNotification.php
new file mode 100644
index 000000000000..17b2aede2b34
--- /dev/null
+++ b/app/Listeners/Invoice/InvoiceEmailedNotification.php
@@ -0,0 +1,56 @@
+invitation;
+
+ foreach($invitation->company->company_users as $company_user)
+ {
+
+ $company_user->user->notify(new InvoiceSentNotification($invitation, $invitation->company));
+
+ }
+
+ if(isset($invitation->company->slack_webhook_url)){
+
+ Notification::route('slack', $invitation->company->slack_webhook_url)
+ ->notify(new InvoiceSentNotification($invitation, $invitation->company, true));
+
+ }
+ }
+}
diff --git a/app/Listeners/Misc/InvitationViewedListener.php b/app/Listeners/Misc/InvitationViewedListener.php
new file mode 100644
index 000000000000..3b3c2d6d151b
--- /dev/null
+++ b/app/Listeners/Misc/InvitationViewedListener.php
@@ -0,0 +1,55 @@
+entity;
+ $invitation = $event->invitation;
+
+ $notification = new EntityViewedNotification($invitation, $entity_name);
+
+ foreach($invitation->company->company_users as $company_user)
+ {
+ $company_user->user->notify($notification);
+ }
+
+ if(isset($invitation->company->slack_webhook_url)){
+
+ $notification->is_system = true;
+
+ Notification::route('slack', $payment->company->slack_webhook_url)
+ ->notify($notification);
+
+ }
+ }
+}
diff --git a/app/Listeners/Payment/PaymentNotification.php b/app/Listeners/Payment/PaymentNotification.php
index 8d906d5fc7ee..ee9d1f562ff2 100644
--- a/app/Listeners/Payment/PaymentNotification.php
+++ b/app/Listeners/Payment/PaymentNotification.php
@@ -14,7 +14,7 @@ namespace App\Listeners\Payment;
use App\Models\Activity;
use App\Models\Invoice;
use App\Models\Payment;
-use App\Notifications\Payment\NewPaymentNotification;
+use App\Notifications\Admin\NewPaymentNotification;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
@@ -41,8 +41,9 @@ class PaymentNotification implements ShouldQueue
{
$payment = $event->payment;
- //$invoices = $payment->invoices;
-
+ //todo need to iterate through teh company user and determine if the user
+ //will receive this notification.
+
foreach($payment->company->company_users as $company_user)
{
$company_user->user->notify(new NewPaymentNotification($payment, $payment->company));
diff --git a/app/Models/Currency.php b/app/Models/Currency.php
index 7a3ebdc2aaff..adc551eb995b 100644
--- a/app/Models/Currency.php
+++ b/app/Models/Currency.php
@@ -18,6 +18,7 @@ class Currency extends StaticModel
public $timestamps = false;
protected $casts = [
+ 'exchange_rate' => 'float',
'swap_currency_symbol' => 'boolean',
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
diff --git a/app/Models/QuoteInvitation.php b/app/Models/QuoteInvitation.php
index 942cd99c1e53..375df47ba7de 100644
--- a/app/Models/QuoteInvitation.php
+++ b/app/Models/QuoteInvitation.php
@@ -71,4 +71,9 @@ class QuoteInvitation extends BaseModel
return sprintf('

%s: %s', $this->signature_base64, ctrans('texts.signed'), $this->createClientDate($this->signature_date, $this->contact->client->timezone()->name));
}
+
+ public function markViewed() {
+ $this->viewed_date = Carbon::now();
+ $this->save();
+ }
}
diff --git a/app/Notifications/Admin/EntityViewedNotification.php b/app/Notifications/Admin/EntityViewedNotification.php
new file mode 100644
index 000000000000..1d6459934abb
--- /dev/null
+++ b/app/Notifications/Admin/EntityViewedNotification.php
@@ -0,0 +1,131 @@
+entity = $invitation->{$entity_name};
+ $this->contact = $invitation->contact;
+ $this->company = $invitation->company;
+ $this->settings = $this->entity->client->getMergedSettings();
+ $this->is_system = $is_system;
+ }
+
+ /**
+ * Get the notification's delivery channels.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function via($notifiable)
+ {
+
+ return $this->is_system ? ['slack'] : ['mail'];
+ }
+
+ /**
+ * Get the mail representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return \Illuminate\Notifications\Messages\MailMessage
+ */
+ public function toMail($notifiable)
+ {
+ $data = $this->buildDataArray();
+ $subject = $this->buildSubject();
+
+ return (new MailMessage)
+ ->subject($subject)
+ ->markdown('email.admin.generic', $data);
+ }
+
+ /**
+ * Get the array representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function toArray($notifiable)
+ {
+ return [
+ //
+ ];
+ }
+
+ public function toSlack($notifiable)
+ {
+ $logo = $this->company->present()->logo();
+ $amount = Number::formatMoney($this->entity->amount, $this->entity->client);
+
+ return (new SlackMessage)
+ ->success()
+ ->from(ctrans('texts.notification_bot'))
+ ->image($logo)
+ ->content(ctrans("texts.notification_{$this->entity_name}_viewed",
+ [
+ 'amount' => $amount,
+ 'client' => $this->contact->present()->name(),
+ $this->entity_name => $this->entity->number
+ ]));
+
+ }
+
+
+ private function buildDataArray()
+ {
+
+ $amount = Number::formatMoney($this->entity->amount, $this->entity->client);
+ $subject = ctrans("texts.notification_{$this->entity_name}_viewed_subject",
+ [
+ 'client' => $this->contact->present()->name(),
+ $this->entity_name => $this->entity->number,
+ ]);
+
+ $data = [
+ 'title' => $subject,
+ 'message' => ctrans("texts.notification_{$this->entity_name}_viewed",
+ [
+ 'amount' => $amount,
+ 'client' => $this->contact->present()->name(),
+ $this->entity_name => $this->entity->number,
+ ]),
+ 'url' => config('ninja.site_url') . "/{$this->entity_name}s/" . $this->entity->hashed_id,
+ 'button' => ctrans("texts.view_{$this->entity_name}"),
+ 'signature' => $this->settings->email_signature,
+ 'logo' => $this->company->present()->logo(),
+ ];
+
+ }
+}
diff --git a/app/Notifications/Admin/InvoiceSentNotification.php b/app/Notifications/Admin/InvoiceSentNotification.php
new file mode 100644
index 000000000000..3c3a67e4a202
--- /dev/null
+++ b/app/Notifications/Admin/InvoiceSentNotification.php
@@ -0,0 +1,142 @@
+invoice = $invitation->invoice;
+ $this->contact = $invitation->contact;
+ $this->company = $company;
+ $this->settings = $this->invoice->client->getMergedSettings();
+ $this->is_system = $is_system;
+ }
+
+ /**
+ * Get the notification's delivery channels.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function via($notifiable)
+ {
+
+ return $this->is_system ? ['slack'] : ['mail'];
+ }
+
+ /**
+ * Get the mail representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return \Illuminate\Notifications\Messages\MailMessage
+ */
+ public function toMail($notifiable)
+ {
+
+ $amount = Number::formatMoney($this->invoice->amount, $this->invoice->client);
+ $subject = ctrans('texts.notification_invoice_sent_subject',
+ [
+ 'client' => $this->contact->present()->name(),
+ 'invoice' => $this->invoice->number,
+ ]);
+
+ $data = [
+ 'title' => $subject,
+ 'message' => ctrans('texts.notification_invoice_sent',
+ [
+ 'amount' => $amount,
+ 'client' => $this->contact->present()->name(),
+ 'invoice' => $this->invoice->number,
+ ]),
+ 'url' => config('ninja.site_url') . '/invoices/' . $this->invoice->hashed_id,
+ 'button' => ctrans('texts.view_invoice'),
+ 'signature' => $this->settings->email_signature,
+ 'logo' => $this->company->present()->logo(),
+ ];
+
+
+ return (new MailMessage)
+ ->subject($subject)
+ ->markdown('email.admin.generic', $data);
+
+
+ }
+
+ /**
+ * Get the array representation of the notification.
+ *
+ * @param mixed $notifiable
+ * @return array
+ */
+ public function toArray($notifiable)
+ {
+ return [
+ //
+ ];
+ }
+
+ public function toSlack($notifiable)
+ {
+ $logo = $this->company->present()->logo();
+ $amount = Number::formatMoney($this->invoice->amount, $this->invoice->client);
+
+ // return (new SlackMessage)
+ // ->success()
+ // ->from(ctrans('texts.notification_bot'))
+ // ->image($logo)
+ // ->content(ctrans('texts.notification_invoice_sent',
+ // [
+ // 'amount' => $amount,
+ // 'client' => $this->contact->present()->name(),
+ // 'invoice' => $this->invoice->number
+ // ]));
+
+
+ return (new SlackMessage)
+ ->from(ctrans('texts.notification_bot'))
+ ->success()
+ ->image('https://app.invoiceninja.com/favicon-v2.png')
+ ->content(trans('texts.notification_invoice_sent_subject',
+ [
+ 'amount' => $amount,
+ 'client' => $this->contact->present()->name(),
+ 'invoice' => $this->invoice->number
+ ]))
+ ->attachment(function ($attachment) use($amount){
+ $attachment->title(ctrans('texts.invoice_number_placeholder', ['invoice' => $this->invoice->number]), 'http://linky')
+ ->fields([
+ ctrans('texts.client') => $this->contact->present()->name(),
+ ctrans('texts.amount') => $amount,
+ ]);
+ });
+ }
+
+}
diff --git a/app/Notifications/Ninja/InvoiceViewedNotification.php b/app/Notifications/Admin/InvoiceViewedNotification.php
similarity index 96%
rename from app/Notifications/Ninja/InvoiceViewedNotification.php
rename to app/Notifications/Admin/InvoiceViewedNotification.php
index dc1d1c2488dd..9f2037e0d9fb 100644
--- a/app/Notifications/Ninja/InvoiceViewedNotification.php
+++ b/app/Notifications/Admin/InvoiceViewedNotification.php
@@ -1,6 +1,6 @@
$amount,
'client' => $this->contact->present()->name(),
- 'invoice' => $this->invoice->number,
- ]);
+ 'invoice' => $this->invoice->number
+ ]));
+
}
}
diff --git a/app/Notifications/Payment/NewPartialPaymentNotification.php b/app/Notifications/Admin/NewPartialPaymentNotification.php
similarity index 99%
rename from app/Notifications/Payment/NewPartialPaymentNotification.php
rename to app/Notifications/Admin/NewPartialPaymentNotification.php
index e1d303eac83a..ded25364f3e3 100644
--- a/app/Notifications/Payment/NewPartialPaymentNotification.php
+++ b/app/Notifications/Admin/NewPartialPaymentNotification.php
@@ -1,6 +1,6 @@
[
InvoiceEmailActivity::class,
+ InvoiceEmailedNotification::class,
],
InvoiceWasEmailedAndFailed::class => [
InvoiceEmailFailedActivity::class,
],
+
+ InvitationWasViewed::class => [
+ InvitationViewedListener::class
+ ],
];
diff --git a/app/Utils/Traits/Inviteable.php b/app/Utils/Traits/Inviteable.php
index ca3cc1e42d16..e26b221d46f7 100644
--- a/app/Utils/Traits/Inviteable.php
+++ b/app/Utils/Traits/Inviteable.php
@@ -24,7 +24,7 @@ trait Inviteable
*
* @return string The status.
*/
- public function getStatus() : string
+ public function getStatus() :string
{
$status = '';
@@ -44,7 +44,7 @@ trait Inviteable
return $status;
}
- public function getLink() : string
+ public function getLink() :string
{
$entity_type = strtolower(class_basename($this->entityType()));
@@ -67,4 +67,10 @@ trait Inviteable
}
}
+
+ public function getAdminLink() :string
+ {
+
+ return $this->getLink(). '?is_admin=true';
+ }
}
diff --git a/app/Utils/Traits/SettingsSaver.php b/app/Utils/Traits/SettingsSaver.php
index 48359250b86e..f05f8469559f 100644
--- a/app/Utils/Traits/SettingsSaver.php
+++ b/app/Utils/Traits/SettingsSaver.php
@@ -112,6 +112,11 @@ trait SettingsSaver
private function checkSettingType($settings) : \stdClass
{
$settings = (object)$settings;
+
+ /* Because of the object casting we cannot check pdf_variables */
+ if(property_exists($settings, 'pdf_variables'))
+ unset($settings->pdf_variables);
+
$casts = CompanySettings::$casts;
foreach ($casts as $key => $value) {
diff --git a/database/seeds/DesignSeeder.php b/database/seeds/DesignSeeder.php
index e9737a3eab25..440e0f726202 100644
--- a/database/seeds/DesignSeeder.php
+++ b/database/seeds/DesignSeeder.php
@@ -35,6 +35,23 @@ class DesignSeeder extends Seeder
if(!$d)
Design::create($design);
}
+
+ foreach(Design::all() as $design){
+
+ $class = 'App\Designs\\'.$design->name;
+ $invoice_design = new $class();
+
+ $design_object = new \stdClass;
+ $design_object->include = $invoice_design->include();
+ $design_object->header = $invoice_design->header();
+ $design_object->body = $invoice_design->body();
+ $design_object->table_styles = $invoice_design->table_styles();
+ $design_object->table = $invoice_design->table();
+ $design_object->footer = $invoice_design->footer();
+
+ $design->design = $design_object;
+ $design->save();
+ }
}
}
\ No newline at end of file
diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php
index 7ef674ad7d65..56e738e7d0cf 100644
--- a/resources/lang/en/texts.php
+++ b/resources/lang/en/texts.php
@@ -251,9 +251,13 @@ $LANG = array(
'notification_invoice_paid_subject' => 'Invoice :invoice was paid by :client',
'notification_invoice_sent_subject' => 'Invoice :invoice was sent to :client',
'notification_invoice_viewed_subject' => 'Invoice :invoice was viewed by :client',
+ 'notification_credit_viewed_subject' => 'Credit :credit was viewed by :client',
+ 'notification_quote_viewed_subject' => 'Quote :quote was viewed by :client',
'notification_invoice_paid' => 'A payment of :amount was made by client :client towards Invoice :invoice.',
'notification_invoice_sent' => 'The following client :client was emailed Invoice :invoice for :amount.',
'notification_invoice_viewed' => 'The following client :client viewed Invoice :invoice for :amount.',
+ 'notification_credit_viewed' => 'The following client :client viewed Credit :credit for :amount.',
+ 'notification_quote_viewed' => 'The following client :client viewed Quote :quote for :amount.',
'reset_password' => 'You can reset your account password by clicking the following button:',
'secure_payment' => 'Secure Payment',
'card_number' => 'Card Number',
@@ -3124,8 +3128,9 @@ $LANG = array(
'notification_payment_paid' => 'A payment of :amount was made by client :client towards :invoice',
'notification_partial_payment_paid' => 'A partial payment of :amount was made by client :client towards :invoice',
'notification_bot' => 'Notification Bot',
-
+ 'invoice_number_placeholder' => 'Invoice # :invoice',
'email_link_not_working' => 'If button above isn\'t working for you, please click on the link',
+
);
return $LANG;