diff --git a/app/Classes/SSL.php b/app/Classes/SSL.php deleted file mode 100644 index 9cec4e3..0000000 --- a/app/Classes/SSL.php +++ /dev/null @@ -1,106 +0,0 @@ -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; - } -} \ No newline at end of file diff --git a/app/Console/Commands/InvoiceGenerate.php b/app/Console/Commands/InvoiceGenerate.php new file mode 100644 index 0000000..406f937 --- /dev/null +++ b/app/Console/Commands/InvoiceGenerate.php @@ -0,0 +1,74 @@ +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(); + } + } +} \ No newline at end of file diff --git a/app/Interfaces/ServiceItem.php b/app/Interfaces/ServiceItem.php index 1de5743..ba95f36 100644 --- a/app/Interfaces/ServiceItem.php +++ b/app/Interfaces/ServiceItem.php @@ -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 diff --git a/app/Models/Account.php b/app/Models/Account.php index 05f9a26..b6006fe 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -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 * diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index b5c6fb3..e28a808 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -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); + } } \ No newline at end of file diff --git a/app/Models/InvoiceItem.php b/app/Models/InvoiceItem.php index 2785535..1697d04 100644 --- a/app/Models/InvoiceItem.php +++ b/app/Models/InvoiceItem.php @@ -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); } } diff --git a/app/Models/InvoiceItemTax.php b/app/Models/InvoiceItemTax.php index 5d9b4c0..eb01ca7 100644 --- a/app/Models/InvoiceItemTax.php +++ b/app/Models/InvoiceItemTax.php @@ -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() { diff --git a/app/Models/Service.php b/app/Models/Service.php index a02472a..9c06388 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -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; }); } diff --git a/app/Models/Service/SSL.php b/app/Models/Service/SSL.php index 8f4b10e..8d76f1a 100644 --- a/app/Models/Service/SSL.php +++ b/app/Models/Service/SSL.php @@ -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 diff --git a/app/Traits/PushNew.php b/app/Traits/PushNew.php new file mode 100644 index 0000000..656f2f4 --- /dev/null +++ b/app/Traits/PushNew.php @@ -0,0 +1,37 @@ +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; + } +} \ No newline at end of file diff --git a/resources/theme/backend/adminlte/u/service/widgets/information.blade.php b/resources/theme/backend/adminlte/u/service/widgets/information.blade.php index eace789..d0a93d6 100644 --- a/resources/theme/backend/adminlte/u/service/widgets/information.blade.php +++ b/resources/theme/backend/adminlte/u/service/widgets/information.blade.php @@ -23,14 +23,16 @@ Invoiced To {{ $o->invoice_to->format('Y-m-d') }} - - Paid Until - {{ $o->paid_to->format('Y-m-d') }} - + @if($o->paid_to) + + Paid Until + {{ $o->paid_to->format('Y-m-d') }} + + @endif @endif Next Invoice - {{ $o->invoice_next->format('Y-m-d') }} + @if ($o->suspend_billing)@endif{{ $o->invoice_next->format('Y-m-d') }}@if ($o->suspend_billing) SUSPENDED@endif Next Estimated Invoice