1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-05 18:52:44 +01:00

Fixes for pdf_variables validation (#3419)

* Client and System Notifications

* Fix for group settings currency not applying correctly.

* Split head out of design in order to reuse headers and footers

* export the designs

* Fixes for pdf_variables
This commit is contained in:
David Bomba 2020-03-04 22:09:43 +11:00 committed by GitHub
parent d14b21f471
commit 6d5d1da472
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 714 additions and 125 deletions

View File

@ -13,6 +13,7 @@ namespace App\Designs;
abstract class AbstractDesign
{
abstract public function include();
abstract public function header();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -13,7 +13,8 @@ namespace App\Designs;
class Custom extends AbstractDesign
{
private $include;
private $header;
private $body;
@ -26,6 +27,7 @@ class Custom extends AbstractDesign
public function __construct($design)
{
$this->include = $design->include;
$this->header = $design->header;
@ -39,6 +41,11 @@ class Custom extends AbstractDesign
}
public function include()
{
return $this->include;
}
public function header()
{

View File

@ -69,7 +69,8 @@ class Designer {
{
$this->exportVariables($entity)
->setDesign($this->getSection('header'))
->setDesign($this->getSection('include'))
->setDesign($this->getSection('header'))
->setDesign($this->getSection('body'))
->setDesign($this->getTable($entity))
->setDesign($this->getSection('footer'));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -17,10 +17,11 @@ class Modern extends AbstractDesign
public function __construct() {
}
public function header() {
return '
<!DOCTYPE html>
public function include()
{
return '
<!DOCTYPE html>
<html lang="en">
<head>
<title>$number</title>
@ -34,6 +35,14 @@ class Modern extends AbstractDesign
</head>
<body>
';
}
public function header() {
return '
<div class="bg-orange-600 flex justify-between py-12 px-12">
<div class="w-1/2">
<h1 class="text-white font-bold text-5xl">$company.name</h1>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,40 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Events\Misc;
use App\Models\Invoice;
use Illuminate\Queue\SerializesModels;
/**
* Class InvitationWasViewed.
*/
class InvitationWasViewed
{
use SerializesModels;
/**
* @var Invoice
*/
public $invitation;
public $entity;
/**
* Create a new event instance.
*
* @param Invoice $invoice
*/
public function __construct($entity, $invitation)
{
$this->entity = $entity;
$this->invitation = $invitation;
}
}

View File

@ -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());
}

View File

@ -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 {

View File

@ -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);
}

View File

@ -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;

View File

@ -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')]);

View File

@ -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);
}
}

View File

@ -0,0 +1,56 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Listeners\Invoice;
use App\Models\Activity;
use App\Models\ClientContact;
use App\Models\InvoiceInvitation;
use App\Notifications\Admin\InvoiceSentNotification;
use App\Repositories\ActivityRepository;
use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class InvoiceEmailedNotification implements ShouldQueue
{
public function __construct()
{
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
$invitation = $event->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));
}
}
}

View File

@ -0,0 +1,55 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Listeners\Misc;
use App\Notifications\Admin\EntityViewedNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Notification;
class InvitationViewedListener implements ShouldQueue
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct(){}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
$entity_name = $event->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);
}
}
}

View File

@ -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));

View File

@ -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',

View File

@ -71,4 +71,9 @@ class QuoteInvitation extends BaseModel
return sprintf('<img src="data:image/svg+xml;base64,%s"></img><p/>%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();
}
}

View File

@ -0,0 +1,131 @@
<?php
namespace App\Notifications\Admin;
use App\Utils\Number;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class EntityViewedNotification extends Notification implements ShouldQueue
{
use Queueable, Dispatchable;
/**
* Create a new notification instance.
*
* @return void
* @
*/
protected $invitation;
protected $entity_name;
protected $entity;
protected $company;
protected $settings;
public $is_system;
protected $contact;
public function __construct($invitation, $entity_name, $is_system = false, $settings = null)
{
$this->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(),
];
}
}

View File

@ -0,0 +1,142 @@
<?php
namespace App\Notifications\Admin;
use App\Utils\Number;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class InvoiceSentNotification extends Notification implements ShouldQueue
{
use Queueable;
/**
* Create a new notification instance.
*
* @return void
*/
protected $invitation;
protected $invoice;
protected $company;
protected $settings;
public $is_system;
protected $contact;
public function __construct($invitation, $company, $is_system = false, $settings = null)
{
$this->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,
]);
});
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace App\Notifications\Payment;
namespace App\Notifications\Admin;
use App\Utils\Number;
use Illuminate\Bus\Queueable;
@ -116,8 +116,9 @@ class InvoiceViewedNotification extends Notification implements ShouldQueue
[
'amount' => $amount,
'client' => $this->contact->present()->name(),
'invoice' => $this->invoice->number,
]);
'invoice' => $this->invoice->number
]));
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace App\Notifications\Payment;
namespace App\Notifications\Admin;
use App\Mail\Signup\NewSignup;
use App\Utils\Number;

View File

@ -1,6 +1,6 @@
<?php
namespace App\Notifications\Payment;
namespace App\Notifications\Admin;
use App\Mail\Signup\NewSignup;
use App\Utils\Number;

View File

@ -19,6 +19,7 @@ use App\Events\Invoice\InvoiceWasEmailed;
use App\Events\Invoice\InvoiceWasMarkedSent;
use App\Events\Invoice\InvoiceWasPaid;
use App\Events\Invoice\InvoiceWasUpdated;
use App\Events\Misc\InvitationWasViewed;
use App\Events\Payment\PaymentWasCreated;
use App\Events\Payment\PaymentWasDeleted;
use App\Events\Payment\PaymentWasRefunded;
@ -37,8 +38,10 @@ use App\Listeners\Invoice\CreateInvoiceInvitation;
use App\Listeners\Invoice\CreateInvoicePdf;
use App\Listeners\Invoice\InvoiceEmailActivity;
use App\Listeners\Invoice\InvoiceEmailFailedActivity;
use App\Listeners\Invoice\InvoiceEmailedNotification;
use App\Listeners\Invoice\UpdateInvoiceActivity;
use App\Listeners\Invoice\UpdateInvoiceInvitations;
use App\Listeners\Misc\InvitationViewedListener;
use App\Listeners\Payment\PaymentNotification;
use App\Listeners\SendVerificationNotification;
use App\Listeners\SetDBListener;
@ -121,10 +124,15 @@ class EventServiceProvider extends ServiceProvider
],
InvoiceWasEmailed::class => [
InvoiceEmailActivity::class,
InvoiceEmailedNotification::class,
],
InvoiceWasEmailedAndFailed::class => [
InvoiceEmailFailedActivity::class,
],
InvitationWasViewed::class => [
InvitationViewedListener::class
],
];

View File

@ -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';
}
}

View File

@ -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) {

View File

@ -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();
}
}
}

View File

@ -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;