Move email/ resources to mail/, added invoice generated email to admin, updated email template

This commit is contained in:
2024-08-03 10:06:25 +10:00
parent f8453ae391
commit 0469d64577
40 changed files with 439 additions and 213 deletions

View File

@@ -3,7 +3,6 @@
namespace App\Console\Commands;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail;
use Illuminate\Console\Command;
use App\Models\{Invoice,Site};
@@ -15,7 +14,9 @@ class InvoiceEmail extends Command
*
* @var string
*/
protected $signature = 'invoice:email {site} {id}';
protected $signature = 'invoice:email'
.' {--s|site : Site ID}'
.' {id?}';
/**
* The console command description.
@@ -31,21 +32,23 @@ class InvoiceEmail extends Command
*/
public function handle()
{
Config::set('site',Site::findOrFail($this->argument('site')));
Config::set(
'site',
$this->option('site')
? Site::findOrFail($this->option('site'))
: Site::where('url',config('app.url'))->sole()
);
$o = Invoice::findOrFail($this->argument('id'));
$result = Mail::to($o->account->user->email)->send(new \App\Mail\InvoiceEmail($o));
try {
$o->print_status = TRUE;
//$o->reminders = $o->reminders('send');
$o->send();
$o->save();
} catch (\Exception $e) {
dd($e);
}
dump($result->getDebug());
return self::SUCCESS;
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
@@ -14,7 +15,11 @@ class InvoiceGenerate extends Command
*
* @var string
*/
protected $signature = 'invoice:generate {site} {account?} {--p|preview : Preview} {--l|list : List Items}';
protected $signature = 'invoice:generate'
.' {--l|list : List Items}'
.' {--p|preview : Preview}'
.' {--s|site : Site ID}'
.' {id?}';
/**
* The console command description.
@@ -30,37 +35,50 @@ class InvoiceGenerate extends Command
*/
public function handle()
{
Config::set('site',Site::findOrFail($this->argument('site')));
Config::set(
'site',
$this->option('site')
? Site::findOrFail($this->option('site'))
: Site::where('url',config('app.url'))->sole()
);
if ($this->argument('account'))
$accounts = collect()->push(Account::find($this->argument('account')));
if ($this->argument('id'))
$accounts = collect()->push(Account::find($this->argument('id')));
else
$accounts = Account::active()->get();
foreach ($accounts as $o) {
$items = $o->invoice_next(Carbon::now());
if (! $items->count()) {
$this->warn(sprintf('No items for account (%s) [%d]',$o->name,$o->id));
continue;
}
$this->info(sprintf('Account: %s [%d]',$o->name,$o->lid));
$io = new Invoice;
$io->account_id = $o->id;
foreach ($o->services(TRUE)->get() as $so) {
foreach ($so->next_invoice_items(FALSE) as $ooo)
$io->items->push($ooo);
}
foreach ($items as $oo)
$io->items_active->push($oo);
// If there are no items, no reason to do anything
if (! $io->items->count() OR $io->total < 0)
if ($io->total < 0) {
$this->warn(sprintf(' - Invoice totals [%3.2f] - skipping',$io->total));
continue;
}
$io->account_id = $o->id;
if ($this->option('list')) {
$this->warn(sprintf('|%4s|%4s|%-50s|%8s|',
$this->line(sprintf('|%4s|%4s|%-50s|%8s|',
'SID',
'PID',
'Name',
'Amount',
));
foreach ($io->items as $oo) {
foreach ($io->items_active as $oo) {
$this->info(sprintf('|%4s|%4s|%-50s|%8.2f|',
$oo->service_id,
$oo->product_id,
@@ -70,8 +88,9 @@ class InvoiceGenerate extends Command
}
}
//dump($io);
if ($this->option('preview')) {
$this->info(sprintf('Invoice for Account [%d] - [%d] items totalling [%3.2f]',$o->id,$io->items->count(),$io->total));
$this->info(sprintf('=> Invoice for Account [%d] - [%d] items totalling [%3.2f]',$o->id,$io->items_active->count(),$io->total));
continue;
}
@@ -81,5 +100,7 @@ class InvoiceGenerate extends Command
$io->pushNew();
}
return self::SUCCESS;
}
}

View File

@@ -52,7 +52,7 @@ class CancelRequest extends Mailable
}
return $this
->markdown('email.admin.service.cancel')
->markdown('mail.admin.service.cancel')
->subject($subject)
->with(['site'=>$this->service->site]);
}

View File

@@ -52,7 +52,7 @@ class ChangeRequest extends Mailable
}
return $this
->markdown('email.admin.service.change')
->markdown('mail.admin.service.change')
->subject($subject)
->with(['site'=>$this->service->site]);
}

View File

@@ -2,21 +2,20 @@
namespace App\Mail;
use App\Models\Site;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use App\Models\Invoice;
class InvoiceEmail extends Mailable
class InvoiceEmail extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;
public $invoice;
public $site;
protected Invoice $io;
/**
* Create a new message instance.
@@ -25,28 +24,33 @@ class InvoiceEmail extends Mailable
*/
public function __construct(Invoice $o)
{
$this->invoice = $o;
$this->io = $o;
$this->queue = 'user';
}
/**
* Build the message.
*
* @return $this
* Get the message envelope.
*/
public function build()
public function envelope(): Envelope
{
Config::set('site',Site::findOrFail($this->invoice->site_id));
$this->site = config('site');
return new Envelope(
subject: sprintf('Invoice %d for services, due %s',
$this->io->lid,
$this->io->due_at->format('Y-m-d')),
);
}
return $this
->markdown('email.user.invoice',['site'=>config('site')])
->subject(sprintf( 'Invoice: %s - Total: $%s - Due: %s',
$this->invoice->id,
number_format($this->invoice->total,2),
$this->invoice->due_at->format('Y-m-d')))
->with([
'user'=>$this->invoice->account->user,
'site'=>$this->invoice->account->user->site,
]);
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'mail.invoice',
with: [
'io'=>$this->io,
'site'=>$this->io->site,
]
);
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use App\Models\Invoice;
class InvoiceGeneratedAdmin extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;
protected Invoice $io;
/**
* Create a new message instance.
*
* @param Invoice $o
*/
public function __construct(Invoice $o)
{
$this->io = $o;
$this->queue = 'admin';
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: sprintf('Invoice %d generated for %s, due %s',
$this->io->lid,
$this->io->account->name,
$this->io->due_at->format('Y-m-d')),
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'mail.admin.invoice.generated',
with: [
'io'=>$this->io,
'site'=>$this->io->site,
]
);
}
}

View File

@@ -52,7 +52,7 @@ class OrderRequest extends Mailable
}
return $this
->markdown('email.admin.order.approve')
->markdown('mail.admin.order.approve')
->subject($subject)
->with(['site'=>$this->service->site]);
}

View File

@@ -51,7 +51,7 @@ class OrderRequestApprove extends Mailable
}
return $this
->markdown('email.admin.order.approve')
->markdown('mail.admin.order.approve')
->subject($subject)
->with(['site'=>$this->so->site]);
}

View File

@@ -38,7 +38,7 @@ class OrderRequestReject extends Mailable
Config::set('site',$this->service->site);
return $this
->markdown('email.admin.order.reject')
->markdown('mail.admin.order.reject')
->subject(sprintf('Your order: #%s was rejected',$this->service->id))
->with(['site'=>$this->service->site]);
}

View File

@@ -39,7 +39,7 @@ class SocialLink extends Mailable
Config::set('site',$this->site);
return $this
->markdown('email.system.social_link')
->markdown('mail.system.social_link')
->subject('Link your Account')
->with([
'site'=>$this->site,

View File

@@ -36,7 +36,7 @@ class TestEmail extends Mailable
Config::set('site',$this->user->site);
return $this
->markdown('email.system.test_email')
->markdown('mail.system.test_email')
->subject('Just a test...')
->with([
'site'=>$this->user->site,

View File

@@ -39,7 +39,7 @@ class TrafficMismatch extends Mailable
Config::set('site',$x=Site::find(1)); // @todo To auto determine;
return $this
->markdown('email.system.broadband_traffic_mismatch')
->markdown('mail.system.broadband_traffic_mismatch')
->subject('Traffic Mismatch for '.$this->date)
->with([
'site'=>$x,

View File

@@ -253,13 +253,15 @@ class Account extends Model implements IDs
/* METHODS */
public function invoice_next(): Collection
public function invoice_next(Carbon $date=NULL): Collection
{
// Collect all the invoice items for our active services
$nextdate = ($x=$this
$svs = $this
->services_active
->filter(fn($item)=>$item->isBilled() && $item->invoice_next)
->sortBy(fn($item)=>(string)$item->invoice_next))
->sortBy(fn($item)=>(string)$item->invoice_next);
// Collect all the invoice items for our active services
$nextdate = $date ?: $svs
->first()
?->invoice_next
->clone();
@@ -271,7 +273,7 @@ class Account extends Model implements IDs
->subDay()
->endOfday();
$items = $x
$items = $svs
->filter(fn($item)=>$item->invoice_next->lessThan($nextitemsdate))
->sortBy(fn($item)=>$item->invoice_next.$item->name)
->map(fn($item)=>$item->next_invoice_items($nextitemsdate))

View File

@@ -75,7 +75,7 @@ class Charge extends Model
return sprintf('%s %s',
$this->description,
$this->getAttribute('attributes')
? join('|',unserialize($this->getAttribute('attributes')))
? $this->getAttribute('attributes')->join('|')
: '');
}

View File

@@ -8,12 +8,14 @@ use Clarkeash\Doorman\Models\Invite;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Mail;
use Leenooks\Casts\LeenooksCarbon;
use Leenooks\Traits\ScopeActive;
use App\Casts\CollectionOrNull;
use App\Interfaces\IDs;
use App\Traits\PushNew;
use App\Mail\{InvoiceEmail,InvoiceGeneratedAdmin};
use App\Traits\{PushNew,SiteID};
/**
* Class Invoice
@@ -36,7 +38,7 @@ use App\Traits\PushNew;
*/
class Invoice extends Model implements IDs
{
use PushNew,ScopeActive;
use PushNew,ScopeActive,SiteID;
protected $casts = [
'created_at' => 'datetime:Y-m-d',
@@ -95,8 +97,8 @@ class Invoice extends Model implements IDs
],
];
// Array of items that can be updated with PushNew
protected $pushable = ['items'];
// Our related items that need to be updated when we call pushNew()
protected $pushable = ['items_active'];
protected $with = [
'items_active:id,start_at,stop_at,quantity,price_base,discount_amt,item_type,product_id,service_id,invoice_id',
@@ -108,6 +110,21 @@ class Invoice extends Model implements IDs
/* STATIC METHODS */
public static function boot()
{
parent::boot();
static::created(function($model) {
// Send an email to an admin that the invoice was created
$uo = User::where('email',config('osb.admin'))->sole();
Mail::to($uo->email)
->send(new InvoiceGeneratedAdmin($model));
// @todo Queue an email to the user
});
}
/**
* This works out what multiplier to use to change billing periods
*
@@ -565,6 +582,28 @@ class Invoice extends Model implements IDs
return parent::save($options);
}
/**
* Record the invoice being sent
*
* @return int
*/
public function send(): int
{
$result = Mail::to($this->account->user->email)
->send(new InvoiceEmail($this));
$this->print_status = TRUE;
if ($this->reminders->has('sent'))
$this->reminders->put('sent',collect($this->reminders->get('sent')));
else
$this->reminders->put('sent',collect());
$this->reminders->get('sent')->push(Carbon::now());
return $result;
}
/**
* Group the invoice items by product ID, returning the number of products and total
*
@@ -574,12 +613,20 @@ class Invoice extends Model implements IDs
{
$return = collect();
foreach ($this->items_active->groupBy('product_id') as $o) {
$po = $o->first()->product;
foreach ($this->items_active->groupBy('product_id') as $id => $o) {
if (! $id) {
$po = new Product;
$po->translate = new ProductTranslate;
$po->translate->name_detail = 'Miscellanious';
} else {
$po = $o->first()->product;
}
$po->count = count($o->pluck('service_id')->unique());
$return->push([
'product' => $o->first()->product,
'product' => $po,
'services' => $o->pluck('service_id')->unique(),
'sub_total' => $o->sum('sub_total'),
'tax_total' => $o->sum('tax'),
@@ -589,4 +636,21 @@ class Invoice extends Model implements IDs
return $return->sortBy('product.name');
}
public function summary_other(): Collection
{
$result = collect();
foreach ($this->items_active->whereNull('service_id') as $o) {
dd($o);
$result->push([
'description' => 'Account Items',
'sub_total' => $o->sub_total,
'tax_total' => $o->tax,
'total' => $o->total,
]);
}
return $result;
}
}

View File

@@ -52,6 +52,22 @@ class InvoiceItem extends Model
127 => 'Rounding', // * SERVICE_ID is NULL, MODULE_ID is NULL, MODULE_REF is NULL
];
/* STATIC */
public static function boot()
{
parent::boot();
static::created(function($model) {
// If this items were a charge, we'll update the charge to processed
if (($model->module_id === 30) && $model->module_ref) {
$o = Charge::findOrfail($model->module_ref);
$o->processed = TRUE;
$o->save();
}
});
}
/* RELATIONS */
public function invoice()

View File

@@ -8,6 +8,7 @@
namespace App\Traits;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Log;
trait PushNew
{
@@ -22,7 +23,7 @@ trait PushNew
// us to recurse into all of these nested relations for the model instance.
foreach ($this->relations as $key => $models) {
// If we are not pushable, jump to the next item.
if (! is_array($this->pushable) OR ! in_array($key,$this->pushable))
if ((! is_array($this->pushable)) || (! in_array($key,$this->pushable)))
continue;
$models = $models instanceof Collection
@@ -32,6 +33,8 @@ trait PushNew
$model->setAttribute($this->getForeignKey(),$this->{$this->getKeyName()});
if (! $model->pushNew()) {
Log::alert('Failed to save model',['attrs'=>$model->getAttributes()]);
return false;
}
}