Enabled console invoice generation
This commit is contained in:
parent
fb9ccd927d
commit
23dc668c65
@ -1,106 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class SSL
|
||||
{
|
||||
// Our CSR
|
||||
private $csr_pem = NULL;
|
||||
// Our Certificate
|
||||
private $crt = [];
|
||||
private $crt_pem = NULL;
|
||||
// Our Key
|
||||
private $key_pem = NULL;
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @return null
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
switch($key)
|
||||
{
|
||||
case 'cn': return $this->cn();
|
||||
case 'dn': return $this->dn();
|
||||
default:
|
||||
throw new \App\Exceptions\SSLUnknownAttribute($key);
|
||||
}
|
||||
}
|
||||
|
||||
private function cn()
|
||||
{
|
||||
$subject = Arr::get($this->crt,'subject');
|
||||
|
||||
if (! $subject AND $this->csr_pem) {
|
||||
$subject = openssl_csr_get_subject($this->csr_pem);
|
||||
}
|
||||
|
||||
return isset($subject['CN']) ? $subject['CN'] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add CSR
|
||||
*
|
||||
* @param $value
|
||||
* @return mixed
|
||||
*/
|
||||
public function csr($value)
|
||||
{
|
||||
$this->csr_pem = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add certificate
|
||||
*
|
||||
* @param $value
|
||||
* @return mixed
|
||||
*/
|
||||
public function crt($value)
|
||||
{
|
||||
$this->crt_pem = $value;
|
||||
$this->crt = openssl_x509_parse($this->crt);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function dn()
|
||||
{
|
||||
$dn = '';
|
||||
|
||||
if ($this->crt_pem) {
|
||||
$this->crt = openssl_x509_parse($this->crt_pem);
|
||||
$dn = Arr::get($this->crt,'name');
|
||||
}
|
||||
|
||||
if (! $dn AND $this->csr_pem) {
|
||||
$dna = openssl_csr_get_subject($this->csr_pem);
|
||||
|
||||
foreach ($dna as $k=>$v) {
|
||||
if ($dn)
|
||||
$dn .= ',';
|
||||
|
||||
$dn .= sprintf('%s=%s',$k,$v);
|
||||
}
|
||||
}
|
||||
|
||||
return $dn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Key
|
||||
*
|
||||
* @param $value
|
||||
* @return mixed
|
||||
*/
|
||||
public function key($value)
|
||||
{
|
||||
$this->key_pem = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
74
app/Console/Commands/InvoiceGenerate.php
Normal file
74
app/Console/Commands/InvoiceGenerate.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Account;
|
||||
use App\Models\Invoice;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class InvoiceGenerate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'invoice:generate {account?} {--p|preview : Preview}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generate Invoices to be Sent';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if ($this->argument('account'))
|
||||
$accounts = collect()->push(Account::find($this->argument('account')));
|
||||
else
|
||||
$accounts = Account::active()->get();
|
||||
|
||||
foreach ($accounts as $o) {
|
||||
$io = new Invoice;
|
||||
|
||||
foreach ($o->services(TRUE)->get() as $so) {
|
||||
foreach ($so->next_invoice_items(FALSE) as $ooo)
|
||||
$io->items->push($ooo);
|
||||
}
|
||||
|
||||
// If there are no items, no reason to do anything
|
||||
if (! $io->items->count())
|
||||
continue;
|
||||
|
||||
$io->account_id = $o->id;
|
||||
|
||||
if ($this->option('preview')) {
|
||||
$this->info(sprintf('Invoice for Account [%d] - [%d] items totalling [%3.2f]',$o->id,$io->items->count(),$io->total));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save the invoice
|
||||
$io->site_id = 1; // @todo
|
||||
$io->active = 1;
|
||||
|
||||
$io->pushNew();
|
||||
}
|
||||
}
|
||||
}
|
@ -9,14 +9,14 @@ interface ServiceItem
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getServiceDescriptionAttribute():string;
|
||||
public function getServiceDescriptionAttribute(): string;
|
||||
|
||||
/**
|
||||
* Return the Service Name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getServiceNameAttribute():string;
|
||||
public function getServiceNameAttribute(): string;
|
||||
|
||||
/**
|
||||
* Is this service in a contract
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
use App\Traits\NextKey;
|
||||
@ -61,7 +62,7 @@ class Account extends Model
|
||||
{
|
||||
$query = $this->hasMany(Service::class);
|
||||
|
||||
return $active ? $query->where('active','=',TRUE) : $query;
|
||||
return $active ? $query->active() : $query;
|
||||
}
|
||||
|
||||
public function user()
|
||||
@ -71,6 +72,14 @@ class Account extends Model
|
||||
|
||||
/** SCOPES */
|
||||
|
||||
/**
|
||||
* Only query active categories
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('active',TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a record
|
||||
*
|
||||
|
@ -2,12 +2,24 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
use App\Traits\NextKey;
|
||||
use App\Traits\PushNew;
|
||||
|
||||
class Invoice extends Model
|
||||
{
|
||||
use NextKey,PushNew;
|
||||
const RECORD_ID = 'invoice';
|
||||
public $incrementing = FALSE;
|
||||
|
||||
protected $table = 'ab_invoice';
|
||||
const CREATED_AT = 'date_orig';
|
||||
const UPDATED_AT = 'date_last';
|
||||
|
||||
protected $dates = ['date_orig','due_date'];
|
||||
public $dateFormat = 'U';
|
||||
|
||||
protected $appends = [
|
||||
'date_due',
|
||||
@ -183,4 +195,23 @@ class Invoice extends Model
|
||||
return $item->product_id == $po->id AND $item->service_id == $so->id;
|
||||
})->filter()->sortBy('item_type');
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically set our due_date at save time.
|
||||
*
|
||||
* @param array $options
|
||||
* @return bool
|
||||
*/
|
||||
public function save(array $options = []) {
|
||||
// Automatically set the date_due attribute for new records.
|
||||
if (! $this->exists AND ! $this->due_date) {
|
||||
$this->due_date = $this->items->min('date_start');
|
||||
|
||||
// @todo This 7 days should be sysetm configurable
|
||||
if (($x=Carbon::now()->addDay(7)) > $this->due_date)
|
||||
$this->due_date = $x;
|
||||
}
|
||||
|
||||
return parent::save($options);
|
||||
}
|
||||
}
|
@ -6,14 +6,22 @@ use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
use App\Traits\NextKey;
|
||||
use App\Traits\PushNew;
|
||||
use Leenooks\Carbon;
|
||||
|
||||
class InvoiceItem extends Model
|
||||
{
|
||||
protected $dates = ['date_start','date_stop'];
|
||||
public $dateFormat = 'U';
|
||||
use NextKey,PushNew;
|
||||
const RECORD_ID = 'invoice_item';
|
||||
public $incrementing = FALSE;
|
||||
|
||||
protected $table = 'ab_invoice_item';
|
||||
const CREATED_AT = 'date_orig';
|
||||
const UPDATED_AT = 'date_last';
|
||||
|
||||
protected $dates = ['date_start','date_stop'];
|
||||
public $dateFormat = 'U';
|
||||
|
||||
private $_tax = 0;
|
||||
|
||||
@ -115,6 +123,7 @@ class InvoiceItem extends Model
|
||||
{
|
||||
return $this->quantity * $this->price_base;
|
||||
}
|
||||
|
||||
public function getTaxAttribute()
|
||||
{
|
||||
if (! $this->_tax)
|
||||
@ -149,6 +158,7 @@ class InvoiceItem extends Model
|
||||
$iit = new InvoiceItemTax;
|
||||
$iit->tax_id = $to->id;
|
||||
$iit->amount = round($this->quantity*$this->price_base*$to->rate,3);
|
||||
$iit->site_id = 1;
|
||||
$this->taxes->push($iit);
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,20 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
use App\Traits\NextKey;
|
||||
use App\Traits\PushNew;
|
||||
|
||||
class InvoiceItemTax extends Model
|
||||
{
|
||||
use NextKey,PushNew;
|
||||
const RECORD_ID = 'invoice_item_tax';
|
||||
public $incrementing = FALSE;
|
||||
|
||||
protected $table = 'ab_invoice_item_tax';
|
||||
const CREATED_AT = 'date_orig';
|
||||
const UPDATED_AT = NULL;
|
||||
|
||||
public $dateFormat = 'U';
|
||||
|
||||
public function invoice_item()
|
||||
{
|
||||
|
@ -468,7 +468,15 @@ class Service extends Model
|
||||
*/
|
||||
public function getInvoiceToAttribute()
|
||||
{
|
||||
return ($x=$this->invoice_items->filter(function($item) { return $item->item_type === 0;}))->count() ? $x->last()->date_stop : NULL;
|
||||
$result = ($x=$this->invoice_items->filter(function($item) { return $item->item_type === 0;}))->count()
|
||||
? $x->last()->date_stop
|
||||
: NULL;
|
||||
|
||||
// For SSL Certificates, the invoice_to date is the expiry date of the Cert
|
||||
if (is_null($result) AND $this->type->type == 'ssl' AND $this->type->valid_to)
|
||||
return $this->type->valid_to;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getNameAttribute(): string
|
||||
@ -480,13 +488,12 @@ class Service extends Model
|
||||
* Return the short name for the service.
|
||||
*
|
||||
* EG:
|
||||
* For ADSL, this would be the phone number,
|
||||
* For Hosting, this would be the domain name, etc
|
||||
* @deprecated
|
||||
* + For ADSL, this would be the phone number,
|
||||
* + For Hosting, this would be the domain name, etc
|
||||
*/
|
||||
public function getNameShortAttribute()
|
||||
{
|
||||
return $this->model ? $this->type->name : 'NAME UNKNOWN';
|
||||
return $this->type->service_name;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -599,10 +606,10 @@ class Service extends Model
|
||||
/**
|
||||
* Return the service description.
|
||||
* For:
|
||||
* + Broadband, this is the service address
|
||||
* + Domains, blank
|
||||
* + Hosting, blank
|
||||
* + SSL, blank
|
||||
* + Broadband, this is the service address
|
||||
* + Domains, blank
|
||||
* + Hosting, blank
|
||||
* + SSL, blank
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
@ -616,10 +623,10 @@ class Service extends Model
|
||||
/**
|
||||
* Return the service name.
|
||||
* For:
|
||||
* + Broadband, this is the service number
|
||||
* + Domains, this is the full domain name
|
||||
* + Hosting, this is the full domain name
|
||||
* + SSL, this is the DN
|
||||
* + Broadband, this is the service number
|
||||
* + Domains, this is the full domain name
|
||||
* + Hosting, this is the full domain name
|
||||
* + SSL, this is the DN
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
@ -789,30 +796,33 @@ class Service extends Model
|
||||
/**
|
||||
* Generate a collection of invoice_item objects that will be billed for the next invoice
|
||||
*
|
||||
* @param bool $future Next item to be billed (not in the next x days)
|
||||
* @return Collection
|
||||
* @throws Exception
|
||||
*/
|
||||
public function next_invoice_items(bool $future): Collection
|
||||
{
|
||||
if ($this->wasCancelled())
|
||||
if ($this->wasCancelled() OR $this->suspend_billing OR ! $this->active)
|
||||
return collect();
|
||||
|
||||
// If pending, add any connection charges
|
||||
// Connection charges are only charged once
|
||||
if ((! $this->invoice_items->filter(function($item) { return $item->item_type==4; })->sum('total'))
|
||||
AND ($this->isPending() OR is_null($this->invoice_to)))
|
||||
AND ($this->isPending() OR is_null($this->invoice_to))
|
||||
AND $this->product->price($this->recur_schedule,'price_setup'))
|
||||
{
|
||||
$o = new InvoiceItem;
|
||||
|
||||
$o->active = TRUE;
|
||||
$o->service_id = $this->id;
|
||||
$o->product_id = $this->product_id;
|
||||
$o->item_type = 4; // @todo change to const or something
|
||||
$o->price_base = $this->price ?: $this->product->price($this->recur_schedule,'price_setup'); // @todo change to a method in this class
|
||||
$o->item_type = 4; // @todo change to const or something
|
||||
$o->price_base = $this->product->price($this->recur_schedule,'price_setup'); // @todo change to a method in this class
|
||||
//$o->recurring_schedule = $this->recur_schedule;
|
||||
$o->date_start = $this->invoice_next;
|
||||
$o->date_stop = $this->invoice_next;
|
||||
$o->quantity = 1;
|
||||
$o->site_id = 1; // @todo
|
||||
|
||||
$o->addTaxes($this->account->country->taxes);
|
||||
$this->invoice_items->push($o);
|
||||
@ -820,7 +830,8 @@ class Service extends Model
|
||||
|
||||
// If the service is active, there will be service charges
|
||||
if ((! $this->invoice_items->filter(function($item) { return $item->item_type==0 AND ! $item->exists; })->count())
|
||||
AND ($this->active OR $this->isPending()))
|
||||
AND ($this->active OR $this->isPending())
|
||||
AND ($future == FALSE AND ($this->invoice_to < Carbon::now()->addDays(30))))
|
||||
{
|
||||
do {
|
||||
$o = new InvoiceItem;
|
||||
@ -828,35 +839,39 @@ class Service extends Model
|
||||
$o->service_id = $this->id;
|
||||
$o->product_id = $this->product_id;
|
||||
$o->item_type = 0;
|
||||
$o->price_base = $this->price ?: $this->product->price($this->recur_schedule); // @todo change to a method in this class
|
||||
$o->price_base = is_null($this->price)
|
||||
? (is_null($this->price_override) ? $this->product->price($this->recur_schedule) : $this->price_override)
|
||||
: $this->price; // @todo change to a method in this class
|
||||
$o->recurring_schedule = $this->recur_schedule;
|
||||
$o->date_start = $this->invoice_next;
|
||||
$o->date_stop = $this->invoice_next_end;
|
||||
$o->quantity = $this->invoice_next_quantity;
|
||||
$o->site_id = 1; // @todo
|
||||
|
||||
$o->addTaxes($this->account->country->taxes);
|
||||
$this->invoice_items->push($o);
|
||||
} while ($future == FALSE AND ($this->invoice_to < Carbon::now()->addDays(30)));
|
||||
}
|
||||
|
||||
// Add additional charges
|
||||
if (! $this->invoice_items->filter(function($item) { return $item->module_id == 30 AND ! $item->exists; })->count())
|
||||
foreach ($this->charges->filter(function($item) { return ! $item->processed; }) as $oo) {
|
||||
$o = new InvoiceItem;
|
||||
$o->active = TRUE;
|
||||
$o->service_id = $oo->service_id;
|
||||
$o->product_id = $this->product_id;
|
||||
$o->quantity = $oo->quantity;
|
||||
$o->item_type = $oo->type;
|
||||
$o->price_base = $oo->amount;
|
||||
$o->date_start = $oo->date_charge;
|
||||
$o->date_stop = $oo->date_charge;
|
||||
$o->module_id = 30; // @todo This shouldnt be hard coded
|
||||
$o->module_ref = $oo->id;
|
||||
// Add additional charges
|
||||
if (! $this->invoice_items->filter(function($item) { return $item->module_id == 30 AND ! $item->exists; })->count())
|
||||
foreach ($this->charges->filter(function($item) { return ! $item->processed; }) as $oo) {
|
||||
$o = new InvoiceItem;
|
||||
$o->active = TRUE;
|
||||
$o->service_id = $oo->service_id;
|
||||
$o->product_id = $this->product_id;
|
||||
$o->quantity = $oo->quantity;
|
||||
$o->item_type = $oo->type;
|
||||
$o->price_base = $oo->amount;
|
||||
$o->date_start = $oo->date_charge;
|
||||
$o->date_stop = $oo->date_charge;
|
||||
$o->module_id = 30; // @todo This shouldnt be hard coded
|
||||
$o->module_ref = $oo->id;
|
||||
$o->site_id = 1; // @todo
|
||||
|
||||
$o->addTaxes($this->account->country->taxes);
|
||||
$this->invoice_items->push($o);
|
||||
}
|
||||
$o->addTaxes($this->account->country->taxes);
|
||||
$this->invoice_items->push($o);
|
||||
}
|
||||
|
||||
return $this->invoice_items->filter(function($item) { return ! $item->exists; });
|
||||
}
|
||||
|
@ -2,6 +2,9 @@
|
||||
|
||||
namespace App\Models\Service;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Carbon\Carbon;
|
||||
|
||||
use App\Interfaces\ServiceItem;
|
||||
use App\Models\Base\ServiceType;
|
||||
use App\Traits\NextKey;
|
||||
@ -13,32 +16,53 @@ class SSL extends ServiceType implements ServiceItem
|
||||
|
||||
protected $table = 'ab_service__ssl';
|
||||
|
||||
protected $_o = NULL;
|
||||
protected $crt_parse = NULL;
|
||||
protected $public_key = NULL;
|
||||
|
||||
public function getSSLAttribute()
|
||||
public static function boot()
|
||||
{
|
||||
if (is_null($this->_o)) {
|
||||
$this->_o = new \App\Classes\SSL;
|
||||
parent::boot();
|
||||
|
||||
if ($this->cert)
|
||||
$this->_o->crt($this->cert);
|
||||
if ($this->csr)
|
||||
$this->_o->csr($this->csr);
|
||||
if ($this->pk)
|
||||
$this->_o->key($this->pk);
|
||||
}
|
||||
static::retrieved(function($model) {
|
||||
if ($model->cert);
|
||||
$model->crt_parse = collect(openssl_x509_parse($model->cert));
|
||||
|
||||
return $this->_o;
|
||||
if ($model->csr) {
|
||||
$model->public_key = collect(openssl_pkey_get_details(openssl_csr_get_public_key($model->csr)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function getValidToAttribute()
|
||||
{
|
||||
return $this->cert ? Carbon::createFromTimestamp($this->crt_parse->get('validTo_time_t')) : NULL;
|
||||
}
|
||||
|
||||
public function getServiceDescriptionAttribute(): string
|
||||
{
|
||||
return $this->ssl->dn;
|
||||
if ($this->cert)
|
||||
return Arr::get($this->crt_parse,'name');
|
||||
|
||||
else {
|
||||
$dn = '';
|
||||
$dna = openssl_csr_get_subject($this->csr);
|
||||
|
||||
foreach ($dna as $k=>$v) {
|
||||
if ($dn)
|
||||
$dn .= ',';
|
||||
|
||||
$dn .= sprintf('%s=%s',$k,$v);
|
||||
}
|
||||
|
||||
return $dn;
|
||||
}
|
||||
}
|
||||
|
||||
public function getServiceNameAttribute(): string
|
||||
{
|
||||
return $this->ssl->cn;
|
||||
return $this->cert
|
||||
? Arr::get($this->crt_parse,'subject.CN')
|
||||
: Arr::get(openssl_csr_get_subject($this->csr),'CN');
|
||||
}
|
||||
|
||||
public function inContract(): bool
|
||||
|
37
app/Traits/PushNew.php
Normal file
37
app/Traits/PushNew.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Enables a pushnew() method, similar to push() but when all relationships are new records
|
||||
*
|
||||
* Base on Model::push()
|
||||
*/
|
||||
namespace App\Traits;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
trait PushNew
|
||||
{
|
||||
public function pushNew() {
|
||||
if (! $this->save()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// To sync all of the relationships to the database, we will simply spin through
|
||||
// the relationships and save each model via this "pushNew" method, which allows
|
||||
// us to recurse into all of these nested relations for the model instance.
|
||||
foreach ($this->relations as $models) {
|
||||
$models = $models instanceof Collection
|
||||
? $models->all() : [$models];
|
||||
|
||||
foreach (array_filter($models) as $model) {
|
||||
$model->setAttribute($this->getForeignKey(),$this->{$this->getKeyName()});
|
||||
|
||||
if (! $model->pushNew()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -23,14 +23,16 @@
|
||||
<th>Invoiced To</th>
|
||||
<td>{{ $o->invoice_to->format('Y-m-d') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Paid Until</th>
|
||||
<td>{{ $o->paid_to->format('Y-m-d') }}</td>
|
||||
</tr>
|
||||
@if($o->paid_to)
|
||||
<tr>
|
||||
<th>Paid Until</th>
|
||||
<td>{{ $o->paid_to->format('Y-m-d') }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
@endif
|
||||
<tr>
|
||||
<th>Next Invoice</th>
|
||||
<td>{{ $o->invoice_next->format('Y-m-d') }}</td>
|
||||
<td>@if ($o->suspend_billing)<strike>@endif{{ $o->invoice_next->format('Y-m-d') }}@if ($o->suspend_billing)</strike> <strong>SUSPENDED</strong>@endif</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Next Estimated Invoice</th>
|
||||
|
Loading…
Reference in New Issue
Block a user