Enable invoice emailing

This commit is contained in:
Deon George 2020-05-25 17:45:17 +10:00
parent cbffda959d
commit 32c562cf30
No known key found for this signature in database
GPG Key ID: 7670E8DC27415254
8 changed files with 351 additions and 78 deletions

View File

@ -0,0 +1,58 @@
<?php
namespace App\Console\Commands;
use Illuminate\Support\Facades\Mail;
use Illuminate\Console\Command;
use App\Models\Invoice;
class InvoiceEmail extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'invoice:email {id}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Email Invoices to be client';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$o = Invoice::findOrFail($this->argument('id'));
Mail::to($o->account->user->email)->send(new \App\Mail\InvoiceEmail($o));
if (Mail::failures()) {
dump('Failure?');
dump(Mail::failures());
} else {
$o->print_status = TRUE;
$o->reminders = $o->reminders('send');
$o->save();
}
}
}

View File

@ -2,8 +2,11 @@
namespace App\Http\Controllers;
use Clarkeash\Doorman\Exceptions\{DoormanException,ExpiredInviteCode};
use Clarkeash\Doorman\Facades\Doorman;
use Illuminate\Contracts\View\Factory;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Barryvdh\Snappy\Facades\SnappyPdf as PDF;
@ -14,7 +17,8 @@ class UserHomeController extends Controller
{
public function __construct()
{
$this->middleware('auth');
// Route protection is in routes.web
// $this->middleware('auth');
}
/**
@ -65,6 +69,32 @@ class UserHomeController extends Controller
return PDF::loadView('u.invoice', ['o'=>$o])->stream(sprintf('%s.pdf',$o->invoice_account_id));
}
/**
* Enable the user to down an invoice by providing a link in email
*
* @param Invoice $o
* @param string $code
* @return \Illuminate\Http\RedirectResponse|mixed
*/
public function invoice_pdf_email(Invoice $o,string $code)
{
try {
Doorman::redeem($code,$o->account->user->email);
} catch (ExpiredInviteCode $e) {
Log::alert(sprintf('User is using an expired token for invoice [%s] using [%s]',$o->id,$code));
return redirect()->to('/login');
} catch (DoormanException $e) {
Log::alert(sprintf('An attempt to read invoice id [%s] using [%s]',$o->id,$code));
abort(404);
}
return $this->invoice_pdf($o);
}
/**
* Helper to redirect to the old site, when functions are not available in this one.
*

47
app/Mail/InvoiceEmail.php Normal file
View File

@ -0,0 +1,47 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Models\Invoice;
class InvoiceEmail extends Mailable
{
use Queueable, SerializesModels;
public $invoice;
/**
* Create a new message instance.
*
* @param Invoice $o
* @param string $notes
*/
public function __construct(Invoice $o)
{
$this->invoice = $o;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this
->markdown('email.user.invoice')
->subject(sprintf( 'Invoice: %s - Total: $%s - Due: %s',
$this->invoice->id,
number_format($this->invoice->total,2),
$this->invoice->date_due))
->with([
'user'=>$this->invoice->account->user,
'site'=>$this->invoice->account->user->site,
]);
}
}

View File

@ -3,10 +3,13 @@
namespace App\Models;
use Carbon\Carbon;
use Clarkeash\Doorman\Facades\Doorman;
use Clarkeash\Doorman\Models\Invite;
use Illuminate\Database\Eloquent\Model;
use App\Traits\NextKey;
use App\Traits\PushNew;
use Illuminate\Support\Arr;
class Invoice extends Model
{
@ -52,7 +55,7 @@ class Invoice extends Model
public function items()
{
return $this->hasMany(InvoiceItem::class);
return $this->hasMany(InvoiceItem::class)->where('active',1);
}
public function paymentitems()
@ -154,6 +157,29 @@ class Invoice extends Model
return $this->account->country->currency;
}
/**
* Return a download link for non-auth downloads
*
* @return string
*/
public function download_link(): string
{
// Re-use an existing code
$io = Invite::where('for',$this->account->user->email)->first();
$tokendate = ($x=Carbon::now()->addDays(21)) > ($y=$this->due_date->addDays(21)) ? $x : $y;
// Extend the expire date
if ($io AND ($tokendate > $io->valid_until)) {
$io->valid_until = $tokendate;
$io->save();
}
$code = (! $io) ? Doorman::generate()->for($this->account->user->email)->uses(0)->expiresOn($tokendate)->make() : $io->code;
return url('u/invoice',[$this->id,'email',$code]);
}
public function products()
{
$return = collect();
@ -196,6 +222,20 @@ class Invoice extends Model
})->filter()->sortBy('item_type');
}
/**
* @param string $key
* @return string
* @todo Ugly hack to update reminders
*/
public function reminders(string $key) {
$r = unserialize($this->reminders);
if (! Arr::get($r,$key)) {
$r[$key] = time();
return serialize($r);
}
}
/**
* Automatically set our due_date at save time.
*

65
config/doorman.php Normal file
View File

@ -0,0 +1,65 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Invite Table Name
|--------------------------------------------------------------------------
|
*/
'invite_table_name' => 'invites',
/*
|--------------------------------------------------------------------------
| Invite Model Class
|--------------------------------------------------------------------------
|
| This option allows you to override the default model.
| Your model MUST extend the base Invite model.
|
| Default: Clarkeash\Doorman\Models\Invite::class
*/
'invite_model' => Clarkeash\Doorman\Models\Invite::class,
/*
|--------------------------------------------------------------------------
| Default Code Generator
|--------------------------------------------------------------------------
|
| This option controls how the invite codes are generated.
| You should adjust this based on your needs.
|
| Supported: "basic", "uuid"
|
*/
'driver' => env('DOORMAN_DRIVER', 'basic'),
/*
|--------------------------------------------------------------------------
| Driver Configurations
|--------------------------------------------------------------------------
|
| Here are each of the driver configurations for your application.
| You can customize should your application require it.
|
*/
'basic' => [
'length' => 6,
],
/*
|--------------------------------------------------------------------------
| UUID
|--------------------------------------------------------------------------
|
| supported versions: 1,3,4,5
|
| Versions 3 & 5, require 'namespace' and 'name' to be set
|
*/
'uuid' => [
'version' => 4,
],
];

View File

@ -0,0 +1,25 @@
@component('mail::message',['site'=>$site,'heading'=>'Invoice: '.$invoice->id])
Hi {{ isset($user) ? $user->name.',' : '' }}
A new invoice has been generated on your account. A summary of that invoice is below.
@component('mail::table')
| # | ID | Name | Amount |
| -: | - |:-----| ------:|
@foreach ($invoice->products() as $po)
| {{ $po->count }} | {{ $po->product_id }} | {{ $po->name($invoice->account->user->language) }} | ${{ number_format($invoice->items->filter(function($item) use ($po) {return $item->product_id == $po->id; })->sum('total'),$invoice->currency()->rounding) }} |
@endforeach
||| Sub Total | ${{ $invoice->sub_total }} |
||| Tax | ${{ $invoice->tax_total }} |
||| Total | ${{ $invoice->total }} |
@endcomponent
If you would like a PDF copy of that invoice, please click on the link below:
@component('mail::panel',['url'=>$invoice->download_link()])
Download PDF
@endcomponent
Thanks,<br>
{{ config('mail.from.name') }}
@endcomponent

View File

@ -1,117 +1,118 @@
body{
margin: 0 auto;
font-family: 'Bree Serif';
background: #f4f4f4;
color: #6c7584;
font-size: 1.2em;
margin: 0 auto;
font-family: 'Bree Serif';
background: #f4f4f4;
color: #6c7584;
font-size: 1.2em;
}
.header{
background: #232323;
border-bottom: 5px solid #454d59;
padding: 20px 0px 10px 0px;
margin-bottom: 30px;
color: #f4f4f4;
font-weight: 300;
background: #232323;
border-bottom: 5px solid #454d59;
padding: 20px 0px 10px 0px;
margin-bottom: 30px;
color: #f4f4f4;
font-weight: 300;
}
.footer{
background: #232323;
border-top: 5px solid #454d59;
padding: 10px 0px 20px 0px;
margin-top: 30px;
color: #f4f4f4;
font-weight: 100;
background: #232323;
border-top: 5px solid #454d59;
padding: 10px 0px 20px 0px;
margin-top: 30px;
color: #f4f4f4;
font-weight: 100;
}
.subject{
font-weight: 300;
font-family: 'Bree Serif',serif;
font-size: 1.5em;
/* text-align: right; */
font-weight: 300;
font-family: 'Bree Serif',serif;
font-size: 1.5em;
/* text-align: right; */
}
.panel{
background:#454d59;
border-radius: 10px;
padding: 20px;
font-weight: 300;
color: #f4f4f4;
font-size: 1.4em;
display: inline-block
background:#454d59;
border-radius: 10px;
margin-top: 20px;
padding: 20px;
font-weight: 300;
color: #f4f4f4;
font-size: 1.4em;
display: inline-block
}
.light-box{
background: #f9f9f9;
border-radius: 10px;
padding: 10px;
font-weight: 300;
margin-top: 10px;
font-size: 0.8em;
margin-bottom: 10px;
background: #f9f9f9;
border-radius: 10px;
padding: 10px;
font-weight: 300;
margin-top: 10px;
font-size: 0.8em;
margin-bottom: 10px;
}
.main-body{
background: #ffffff;
border-radius: 10px;
color:#6c7584;
font-weight: 400;
padding:10px 20px;
border-top:1px solid #dbdbdb;
border-left:1px solid #dbdbdb;
border-right:1px solid #dbdbdb;
border-bottom:3px solid #dbdbdb;
background: #ffffff;
border-radius: 10px;
color:#6c7584;
font-weight: 400;
padding:10px 20px;
border-top:1px solid #dbdbdb;
border-left:1px solid #dbdbdb;
border-right:1px solid #dbdbdb;
border-bottom:3px solid #dbdbdb;
}
.main-body table thead td{
font-weight: 300;
border-bottom: 1px solid #dbdbdb;
color: #ccc
font-weight: 300;
border-bottom: 1px solid #dbdbdb;
color: #ccc
}
.main-body table td.title{
font-size: 1.1em;
line-height: 20px;
font-size: 1.1em;
line-height: 20px;
}
.main-body table td.title small{
font-weight: 300;
font-size: 0.9em;
color: #6c7584
font-weight: 300;
font-size: 0.9em;
color: #6c7584
}
.main-body .note{
font-size: 0.8em;
font-weight: 300;
font-style: normal;
font-size: 0.8em;
font-weight: 300;
font-style: normal;
}
.panel a{
text-decoration: underline;
color: #f4f4f4;
text-decoration: underline;
color: #f4f4f4;
}
.panel a:hover{
text-decoration: none;
color: #ffffff;
text-decoration: none;
color: #ffffff;
}
/* -- TO VALIDATE -- */
h1{
font-weight: 300;
color: #121212;
font-family: 'Bree Serif',serif;
margin:0 auto;
font-size: 32px;
font-weight: 300;
color: #121212;
font-family: 'Bree Serif',serif;
margin:0 auto;
font-size: 32px;
}
h1 small{font-weight: 200; font-size: 24px;}
h3{
font-weight: 300;
color: #121212;
font-family: 'Bree Serif',serif;
margin: 25px auto;
font-size: 24px;
font-weight: 300;
color: #121212;
font-family: 'Bree Serif',serif;
margin: 25px auto;
font-size: 24px;
}
.free{color:#1cbbb4;}
.paid{color:#0f80bb;}
.links{
margin: 30px auto;
width: 600px;
margin: 30px auto;
width: 600px;
}
.links table td{
padding: 20px;
text-align: center;
font-weight: 300;
color: #333;
font-size:18px;
padding: 20px;
text-align: center;
font-weight: 300;
color: #333;
font-size:18px;
}
.links table td span, .links table td a{font-weight: 400}
.border-l{border-left:1px solid #ccc}

View File

@ -74,6 +74,13 @@ Route::group(['middleware'=>['theme:adminlte-be','auth'],'prefix'=>'u'],function
->middleware('can:progress,o,status');
});
// Doorman Code Routes
Route::group(['middleware'=>['theme:adminlte-be'],'prefix'=>'u'],function() {
Route::get('invoice/{o}/email/{code}','UserHomeController@invoice_pdf_email')
->where('o','[0-9]+')
->where('code','[0-9A-Z]{6}');
});
// Frontend Routes (Non-Authed Users)
Route::group(['middleware'=>['theme:metronic-fe']],function() {
Route::get('/','WelcomeController@index');