mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 05:02:36 +01:00
Workflow settings for quotes & invoices (#3655)
* Fix inputs * Workflow settings: Auto archive - New InvoiceWorkflowSettings job - Updated PaymentCreatedActivity to dispatch the job - New 'Archivable' trait - New 'archived_at' field * Workflow settings: Sending e-mail to client with auto_send * Workflow settings: Quotes * Fix HTML markup * Fix archive & sending * Remove Archivable trait & migration file * Change order of sending the events
This commit is contained in:
parent
3aa884dc11
commit
d516b1b097
29
app/Events/Quote/QuoteWasApproved.php
Normal file
29
app/Events/Quote/QuoteWasApproved.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Quote;
|
||||
|
||||
use App\Models\Quote;
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class QuoteWasApproved
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $quote;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Quote $quote)
|
||||
{
|
||||
$this->quote = $quote;
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\ClientPortal;
|
||||
|
||||
use App\Events\Quote\QuoteWasApproved;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ClientPortal\ProcessQuotesInBulkRequest;
|
||||
use App\Http\Requests\ClientPortal\ShowQuoteRequest;
|
||||
@ -98,6 +99,7 @@ class QuoteController extends Controller
|
||||
if ($process) {
|
||||
foreach ($quotes as $quote) {
|
||||
$quote->service()->approve()->save();
|
||||
event(new QuoteWasApproved($quote));
|
||||
}
|
||||
|
||||
return route('client.quotes.index')->withSuccess('Quote(s) approved successfully.');
|
||||
|
64
app/Jobs/Invoice/InvoiceWorkflowSettings.php
Normal file
64
app/Jobs/Invoice/InvoiceWorkflowSettings.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?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\Jobs\Invoice;
|
||||
|
||||
use App\Mail\Invoices\InvoiceWasPaid;
|
||||
use App\Models\Client;
|
||||
use App\Models\Invoice;
|
||||
use App\Repositories\BaseRepository;
|
||||
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\Mail;
|
||||
|
||||
class InvoiceWorkflowSettings implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $invoice;
|
||||
public $client;
|
||||
private $base_repository;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Invoice $invoice, Client $client = null)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
$this->client = $client ?? $invoice->client;
|
||||
$this->base_repository = new BaseRepository();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if ($this->client->getSetting('auto_archive_invoice')) {
|
||||
/** Throws: Payment amount xxx does not match invoice totals. */
|
||||
$this->base_repository->archive($this->invoice);
|
||||
}
|
||||
|
||||
if ($this->client->getSetting('auto_email_invoice')) {
|
||||
$this->invoice->invitations->each(function ($invitation, $key) {
|
||||
$this->invoice->service()->sendEmail($invitation->contact);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
62
app/Jobs/Quote/QuoteWorkflowSettings.php
Normal file
62
app/Jobs/Quote/QuoteWorkflowSettings.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?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\Jobs\Quote;
|
||||
|
||||
use App\Mail\Quote\QuoteWasApproved;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Client;
|
||||
use App\Repositories\BaseRepository;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class QuoteWorkflowSettings implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $quote;
|
||||
public $client;
|
||||
public $base_repository;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Quote $quote, Client $client = null)
|
||||
{
|
||||
$this->quote = $quote;
|
||||
$this->client = $client ?? $quote->client;
|
||||
$this->base_repository = new BaseRepository();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if ($this->client->getSetting('auto_archive_quote')) {
|
||||
$this->base_repository->archive($this->quote);
|
||||
}
|
||||
|
||||
if ($this->client->getSetting('auto_email_quote')) {
|
||||
$this->quote->invitations->each(function ($invitation, $key) {
|
||||
$this->quote->service()->sendEmail($invitation->contact);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Listeners\Activity;
|
||||
|
||||
use App\Jobs\Invoice\InvoiceWorkflowSettings;
|
||||
use App\Models\Activity;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
@ -54,6 +55,8 @@ class PaymentCreatedActivity implements ShouldQueue
|
||||
foreach ($invoices as $invoice) { //todo we may need to add additional logic if in the future we apply payments to other entity Types, not just invoices
|
||||
$fields->invoice_id = $invoice->id;
|
||||
|
||||
InvoiceWorkflowSettings::dispatchNow($invoice);
|
||||
|
||||
$this->activityRepo->save($fields, $invoice);
|
||||
}
|
||||
|
||||
|
31
app/Listeners/Quote/ReachWorkflowSettings.php
Normal file
31
app/Listeners/Quote/ReachWorkflowSettings.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\Quote;
|
||||
|
||||
use App\Jobs\Quote\QuoteWorkflowSettings;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class ReachWorkflowSettings
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
QuoteWorkflowSettings::dispatchNow($event->quote);
|
||||
}
|
||||
}
|
33
app/Mail/Invoices/InvoiceWasPaid.php
Normal file
33
app/Mail/Invoices/InvoiceWasPaid.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail\Invoices;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InvoiceWasPaid extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
return $this->view('email.invoices.paid');
|
||||
}
|
||||
}
|
33
app/Mail/Quote/QuoteWasApproved.php
Normal file
33
app/Mail/Quote/QuoteWasApproved.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail\Quote;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class QuoteWasApproved extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
return $this->view('email.quotes.approved');
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ use App\Models\PaymentTerm;
|
||||
use App\Services\Invoice\InvoiceService;
|
||||
use App\Services\Ledger\LedgerService;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\Archivable;
|
||||
use App\Utils\Traits\InvoiceEmailBuilder;
|
||||
use App\Utils\Traits\Invoice\ActionsInvoice;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
|
@ -18,6 +18,7 @@ use App\Jobs\Invoice\CreateInvoicePdf;
|
||||
use App\Jobs\Quote\CreateQuotePdf;
|
||||
use App\Models\Filterable;
|
||||
use App\Services\Quote\QuoteService;
|
||||
use App\Utils\Traits\Archivable;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\MakesInvoiceValues;
|
||||
|
@ -158,11 +158,9 @@ class PayPalExpressPaymentDriver extends BasePaymentDriver
|
||||
|
||||
$this->attachInvoices($payment, $request->input('hashed_ids'));
|
||||
|
||||
event(new PaymentWasCreated($payment, $payment->company));
|
||||
|
||||
$payment->service()->UpdateInvoicePayment();
|
||||
|
||||
//UpdateInvoicePayment::dispatchNow($payment, $payment->company);
|
||||
|
||||
event(new PaymentWasCreated($payment, $payment->company));
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment'=>$this->encodePrimaryKey($payment->id)]);
|
||||
}
|
||||
|
@ -344,12 +344,11 @@ class StripePaymentDriver extends BasePaymentDriver
|
||||
|
||||
/* Link invoices to payment*/
|
||||
$this->attachInvoices($payment, $hashed_ids);
|
||||
|
||||
$payment->service()->UpdateInvoicePayment();
|
||||
|
||||
event(new PaymentWasCreated($payment, $payment->company));
|
||||
|
||||
$payment->service()->UpdateInvoicePayment();
|
||||
//UpdateInvoicePayment::dispatchNow($payment, $payment->company);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
[
|
||||
'server_response' => $payment_intent,
|
||||
|
@ -28,6 +28,7 @@ use App\Events\Payment\PaymentWasCreated;
|
||||
use App\Events\Payment\PaymentWasDeleted;
|
||||
use App\Events\Payment\PaymentWasRefunded;
|
||||
use App\Events\Payment\PaymentWasVoided;
|
||||
use App\Events\Quote\QuoteWasApproved;
|
||||
use App\Events\User\UserLoggedIn;
|
||||
use App\Events\User\UserWasCreated;
|
||||
use App\Events\User\UserWasDeleted;
|
||||
@ -50,6 +51,7 @@ use App\Listeners\Invoice\UpdateInvoiceActivity;
|
||||
use App\Listeners\Invoice\UpdateInvoiceInvitations;
|
||||
use App\Listeners\Misc\InvitationViewedListener;
|
||||
use App\Listeners\Payment\PaymentNotification;
|
||||
use App\Listeners\Quote\ReachWorkflowSettings;
|
||||
use App\Listeners\SendVerificationNotification;
|
||||
use App\Listeners\SetDBListener;
|
||||
use App\Listeners\User\DeletedUserActivity;
|
||||
@ -144,10 +146,12 @@ class EventServiceProvider extends ServiceProvider
|
||||
InvitationWasViewed::class => [
|
||||
InvitationViewedListener::class
|
||||
],
|
||||
|
||||
CompanyWasDeleted::class => [
|
||||
DeleteCompanyDocuments::class,
|
||||
],
|
||||
QuoteWasApproved::class => [
|
||||
ReachWorkflowSettings::class,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -71,7 +71,7 @@ class BaseRepository
|
||||
}
|
||||
|
||||
$entity->delete();
|
||||
|
||||
|
||||
$className = $this->getEventClass($entity, 'Archived');
|
||||
|
||||
if (class_exists($className)) {
|
||||
|
26
resources/views/email/invoices/paid.blade.php
Normal file
26
resources/views/email/invoices/paid.blade.php
Normal file
@ -0,0 +1,26 @@
|
||||
@component('email.template.master', ['design' => 'light'])
|
||||
|
||||
@slot('header')
|
||||
@component('email.components.header', ['p' => 'Payment for your invoice has been completed!', 'logo' => 'https://www.invoiceninja.com/wp-content/uploads/2019/01/InvoiceNinja-Logo-Round-300x300.png'])
|
||||
Invoice paid
|
||||
@endcomponent
|
||||
|
||||
@endslot
|
||||
|
||||
@slot('greeting')
|
||||
Hello,
|
||||
@endslot
|
||||
|
||||
We want to inform you that payment was completed for your invoice. Amount: $10,000.00.
|
||||
|
||||
@component('email.components.button', ['url' => 'https://invoiceninja.com', 'show_link' => true])
|
||||
Visit InvoiceNinja
|
||||
@endcomponent
|
||||
|
||||
@slot('below_card')
|
||||
@component('email.components.footer', ['url' => 'https://invoiceninja.com', 'url_text' => '© InvoiceNinja'])
|
||||
For any info, please visit InvoiceNinja.
|
||||
@endcomponent
|
||||
@endslot
|
||||
|
||||
@endcomponent
|
26
resources/views/email/quotes/approved.blade.php
Normal file
26
resources/views/email/quotes/approved.blade.php
Normal file
@ -0,0 +1,26 @@
|
||||
@component('email.template.master', ['design' => 'light'])
|
||||
|
||||
@slot('header')
|
||||
@component('email.components.header', ['p' => 'Your quote was approved!', 'logo' => 'https://www.invoiceninja.com/wp-content/uploads/2019/01/InvoiceNinja-Logo-Round-300x300.png'])
|
||||
Quote approved
|
||||
@endcomponent
|
||||
|
||||
@endslot
|
||||
|
||||
@slot('greeting')
|
||||
Hello,
|
||||
@endslot
|
||||
|
||||
We want to inform you that quote was approved. Put nicer text here.
|
||||
|
||||
@component('email.components.button', ['url' => 'https://invoiceninja.com', 'show_link' => true])
|
||||
Visit InvoiceNinja
|
||||
@endcomponent
|
||||
|
||||
@slot('below_card')
|
||||
@component('email.components.footer', ['url' => 'https://invoiceninja.com', 'url_text' => '© InvoiceNinja'])
|
||||
For any info, please visit InvoiceNinja.
|
||||
@endcomponent
|
||||
@endslot
|
||||
|
||||
@endcomponent
|
@ -37,7 +37,7 @@
|
||||
{{ ctrans('texts.name') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<input class="input" id="cardholder-name" type="text" placeholder="{{ ctrans('texts.name') }}">
|
||||
<input class="input w-full" id="cardholder-name" type="text" placeholder="{{ ctrans('texts.name') }}">
|
||||
</dd>
|
||||
</div><div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
@ -52,7 +52,7 @@
|
||||
{{ ctrans('texts.save_as_default') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<input type="checkbox" class="form-check" name="proxy_is_default"
|
||||
<input type="checkbox" class="form-checkbox" name="proxy_is_default"
|
||||
id="proxy_is_default"/>
|
||||
</dd>
|
||||
</div>
|
||||
|
@ -4,6 +4,7 @@
|
||||
@push('head')
|
||||
<meta name="stripe-publishable-key" content="{{ $gateway->getPublishableKey() }}">
|
||||
<meta name="using-token" content="{{ boolval($token) }}">
|
||||
<meta name="turbolinks-visit-control" content="reload">
|
||||
@endpush
|
||||
|
||||
@section('header')
|
||||
@ -62,7 +63,7 @@
|
||||
{{ ctrans('texts.name') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<input class="input" id="cardholder-name" type="text"
|
||||
<input class="input w-full" id="cardholder-name" type="text"
|
||||
placeholder="{{ ctrans('texts.name') }}">
|
||||
</dd>
|
||||
</div>
|
||||
|
@ -31,8 +31,8 @@
|
||||
@endsection
|
||||
|
||||
@section('body')
|
||||
<div class="flex justify-between items-center">
|
||||
<span>{{ ctrans('texts.with_selected') }}</span>
|
||||
<div class="flex justify-end items-center">
|
||||
<span class="text-sm mr-2">{{ ctrans('texts.with_selected') }}:</span>
|
||||
<form action="{{ route('client.invoices.bulk') }}" method="post" id="bulkActions">
|
||||
@csrf
|
||||
<button type="submit" class="button button-primary" name="action" value="download">{{ ctrans('texts.download') }}</button>
|
||||
|
@ -5,6 +5,7 @@
|
||||
<meta name="show-invoice-terms" content="{{ $settings->show_accept_invoice_terms ? true : false }}">
|
||||
<meta name="require-invoice-signature" content="{{ $settings->require_invoice_signature ? true : false }}">
|
||||
<script src="https://cdn.jsdelivr.net/npm/signature_pad@2.3.2/dist/signature_pad.min.js"></script>
|
||||
<meta name="turbolinks-visit-control" content="reload">
|
||||
@endpush
|
||||
|
||||
@section('body')
|
||||
|
Loading…
Reference in New Issue
Block a user