More Service display

This commit is contained in:
Deon George 2019-07-02 15:28:27 +10:00
parent 6103b61265
commit 59a8ef2476
No known key found for this signature in database
GPG Key ID: 7670E8DC27415254
14 changed files with 311 additions and 14 deletions

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use App\Models\{Invoice,Service};
use App\User;
use PDF;

View File

@ -0,0 +1,14 @@
<?php
namespace App\Models\Base;
use Illuminate\Database\Eloquent\Model;
use App\Models\Product;
//@todo column prod_plugin_file should no longer be required
abstract class ProductType extends Model
{
public $timestamps = FALSE;
public $dateFormat = 'U';
}

View File

@ -8,7 +8,7 @@ class Invoice extends Model
{
protected $table = 'ab_invoice';
protected $dates = ['date_orig','due_date'];
protected $with = ['account.country.currency','items.product','items.service','items.taxes','paymentitems'];
protected $with = ['account.country.currency','items','paymentitems'];
protected $appends = [
'date_due',

View File

@ -3,14 +3,26 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use App\Traits\NextKey;
class Product extends Model
{
use NextKey;
const RECORD_ID = 'product';
protected $table = 'ab_product';
protected $with = ['descriptions'];
const CREATED_AT = 'date_orig';
const UPDATED_AT = 'date_last';
public $incrementing = FALSE;
public $dateFormat = 'U';
public function descriptions()
{
return $this->hasMany(ProductTranslate::class);
@ -21,6 +33,16 @@ class Product extends Model
return $this->hasMany(Service::class);
}
/**
* Return a child model with details of the service
*
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function type()
{
return $this->morphTo(null,'model','prod_plugin_data');
}
/**
* Get the service category (from the product)
*
@ -91,6 +113,14 @@ class Product extends Model
return $this->descriptions->where('language_id',$lo->id)->first()->description_short;
}
public function getNameShortAttribute(Language $lo=NULL)
{
if (is_null($lo))
$lo = $this->getDefaultLanguage();
return $this->descriptions->where('language_id',$lo->id)->first()->name;
}
public function getProductTypeAttribute()
{
return $this->plugin()->product->name;
@ -149,6 +179,11 @@ class Product extends Model
}
}
public function price(int $period)
{
return Arr::get($this->price_array,sprintf('%s.1.price_base',$period));
}
public function PricePeriods()
{
return [

View File

@ -0,0 +1,36 @@
<?php
namespace App\Models\Product;
use App\Traits\NextKey;
use App\Models\AdslSupplierPlan;
class Adsl extends \App\Models\Base\ProductType
{
use NextKey;
const RECORD_ID = 'adsl_plan';
protected $table = 'ab_adsl_plan';
public function product()
{
return $this->hasOne(AdslSupplierPlan::class,'id','adsl_supplier_plan_id');
}
public function __get($key)
{
switch($key)
{
case 'base_down_peak':
return $this->attributes['base_down_peak']/$this->attributes['metric'];
case 'base_down_peak':
return $this->attributes['base_down_offpeak']/$this->attributes['metric'];
case 'speed':
return $this->product->speed;
}
// If we dont have a specific key, we'll resolve it normally
return parent::__get($key);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Models\Product;
use App\Traits\NextKey;
class Domain extends \App\Models\Base\ProductType
{
use NextKey;
const RECORD_ID = '';
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Models\Product;
use App\Traits\NextKey;
class Host extends \App\Models\Base\ProductType
{
use NextKey;
const RECORD_ID = '';
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Models\Product;
use App\Traits\NextKey;
class SSL extends \App\Models\Base\ProductType
{
use NextKey;
const RECORD_ID = 'ssl';
protected $table = 'ab_ssl';
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Models\Product;
use App\Traits\NextKey;
class Voip extends \App\Models\Base\ProductType
{
use NextKey;
const RECORD_ID = '';
}

View File

@ -2,7 +2,6 @@
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
@ -20,9 +19,9 @@ class Service extends Model
public $incrementing = FALSE;
protected $table = 'ab_service';
protected $with = ['product.descriptions','account.language'];
protected $dates = ['date_last_invoice','date_next_invoice'];
public $dateFormat = 'U';
protected $with = ['product.descriptions','account.language','type'];
protected $casts = [
'order_info'=>'array',
@ -97,9 +96,23 @@ class Service extends Model
return $this->belongsTo(Account::class);
}
/**
* Return Charges associated with this Service
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function charges()
{
return $this->hasMany(Charge::class)
->where('active','=',TRUE)
->orderBy('date_orig');
}
// @todo changed to invoiced_items
public function invoice_items($active=TRUE)
{
$query = $this->hasMany(InvoiceItem::class)
->where('item_type','=',0)
->orderBy('date_orig');
if ($active)
@ -131,6 +144,7 @@ class Service extends Model
/**
* Account that ordered the service
*
* @todo changed to orderedby
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function orderby()
@ -215,11 +229,7 @@ class Service extends Model
public function getBillingPriceAttribute(): float
{
if ($this->price)
return $this->addtax($this->price);
dd($this->product->price_group,$this);
return $this->cost;
return $this->addTax($this->price ?: $this->product->price($this->recur_schedule));
}
/**
@ -248,11 +258,36 @@ class Service extends Model
* @todo This function negates the need for date_next_invoice
* @return null
*/
public function getInvoiceNextAttribute(): Carbon
public function getInvoiceNextAttribute()
{
$last = $this->getInvoiceToAttribute();
$date = $last ? $last->addDay() : now();
return $last ? $last->addDay() : now();
return request()->wantsJson() ? $date->format('Y-m-d') : $date;
}
public function getInvoiceNextEndAttribute()
{
switch ($this->recur_schedule)
{
// Weekly
case 0: $date = $this->getInvoiceNextAttribute()->addWeek(); break;
// Monthly
case 1: $date = $this->getInvoiceNextAttribute()->addMonth(); break;
// Quarterly
case 2: $date = $this->getInvoiceNextAttribute()->addQuarter(); break;
// Half Yearly
case 3: $date = $this->getInvoiceNextAttribute()->addQuarter(2); break;
// Yearly
case 4: $date = $this->getInvoiceNextAttribute()->addYear(); break;
// Two Yearly
case 5: $date = $this->getInvoiceNextAttribute()->addYear(2); break;
// Three Yearly
case 6: $date = $this->getInvoiceNextAttribute()->addYear(3); break;
default: throw new \Exception('Unknown recur_schedule');
}
return $date->subDay();
}
/**
@ -263,6 +298,11 @@ class Service extends Model
return $this->invoice_items->count() ? $this->invoice_items->last()->date_stop : NULL;
}
public function getNameAttribute(): string
{
return $this->product->name_short.': '.$this->getNameShortAttribute();
}
/**
* Return the short name for the service.
*
@ -448,6 +488,8 @@ class Service extends Model
public function invoices_due(): Collection
{
$this->load('invoice_items.invoice');
return $this->invoice_items->filter(function($item) {
return $item->invoice->due > 0;
});
@ -464,6 +506,28 @@ class Service extends Model
return $this->active OR ($this->order_status AND ! in_array($this->order_status,$this->inactive_status));
}
public function next_invoice_items()
{
$result = collect();
$result->push([
'item'=>0,
'desc'=>sprintf('Product/Service [%s->%s]',
$this->invoice_next->format('Y-m-d'),
$this->invoice_next_end->format('Y-m-d')),
'amt'=>$this->getBillingPriceAttribute()]);
foreach ($this->charges->filter(function($item) { return ! $item->processed; }) as $o)
{
$result->push([
'item'=>5, // @tod Charges
'desc'=>sprintf('%d@%3.2f - %s',$o->quantity,$this->addTax($o->amount),$o->name),
'amt'=>$this->addTax($o->amount*$o->quantity)]);
}
return $result;
}
/**
* @todo
* @param string $status

View File

@ -17,7 +17,6 @@ class User extends Authenticatable
use HasApiTokens,Notifiable,UserSwitch,HasQuickBooksToken;
protected $dates = ['created_at','updated_at','last_access'];
protected $with = ['accounts.services'];
/**
* The attributes that are mass assignable.

View File

@ -0,0 +1,57 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddModelToProduct extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('ab_product', function (Blueprint $table) {
$table->string('model')->nullable();
});
// Go through our services and add the relation
foreach (\App\Models\Product\Adsl::all() as $o)
$this->update($o,'ADSL');
/*
foreach (\App\Models\Product\Domain::all() as $o)
$this->update($o);
foreach (\App\Models\Product\Host::all() as $o)
$this->update($o);
*/
foreach (\App\Models\Product\SSL::all() as $o)
$this->update($o,'SSL');
/*
foreach (\App\Models\Product\Voip::all() as $o)
$this->update($o);
*/
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('ab_product', function (Blueprint $table) {
$table->dropColumn('model');
});
}
function update(\Illuminate\Database\Eloquent\Model $o,$file)
{
foreach (\App\Models\Product::where('prod_plugin_file',$file)->where('prod_plugin_data',$o->id)->get() as $oo)
{
$oo->model = get_class($o);
$oo->save();
}
}
}

View File

@ -0,0 +1,18 @@
<div class="card">
<div class="card-header">
<strong>Next Invoice Details</strong>
</div>
<div class="card-body">
<table class="table table-borderless">
<tr>
<th colspan="3">{{ $o->name }}</th><th class="text-right">{{ number_format($o->next_invoice_items()->sum('amt'),2) }}</th>
</tr>
@foreach ($o->next_invoice_items() as $oo)
<tr>
<td class="pt-0 pb-1">&nbsp;</td><td class="pt-0 pb-1">{{ $oo['desc'] }}</td><td class="text-right pt-0 pb-1">{{ number_format($oo['amt'],2) }}</td>
</tr>
@endforeach
</table>
</div>
</div>

View File

@ -84,12 +84,20 @@
<table class="table table-borderless">
<tr>
<th>Speed</th>
<td>TBA</td>
<td>{{ $o->product->type->speed }}</td>
</tr>
@if ($o->product->type->base_down_peak)
<tr>
<th>Peak Included Downloads</th>
<td>TBA</td>
<td>{{ number_format($o->product->type->base_down_peak,0) }}GB</td>
</tr>
@endif
@if ($o->product->type->base_down_offpeak)
<tr>
<th>Peak Included Downloads</th>
<td>{{ number_format($o->product->type->base_down_offpeak,0) }}MB</td>
</tr>
@endif
<tr>
<th>Traffic Last Month</th>
<td>TBA</td>
@ -111,7 +119,22 @@
</div>
<div class="tab-pane" id="tab-invoice">
@include('common.invoice.widget.list')
<div class="row">
<div class="col-4">
@include('common.service.widget.invoice')
{{-- @todo show list of outstanding invoices --}}
</div>
{{-- Workaround since col-offset-x is not in our CSS? --}}
<div class="col-2">
&nbsp;
</div>
<div class="col-6">
@include('common.invoice.widget.list')
</div>
</div>
</div>
<div class="tab-pane" id="tab-email">