Start of service display

This commit is contained in:
Deon George 2019-06-29 10:14:12 +10:00
parent a426c7b1a4
commit 6103b61265
No known key found for this signature in database
GPG Key ID: 7670E8DC27415254
14 changed files with 426 additions and 33 deletions

View File

@ -4,10 +4,8 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Models\{Account}; use App\Models\{Account,Service\Adsl};
class SearchController extends Controller class SearchController extends Controller
{ {
@ -26,7 +24,7 @@ class SearchController extends Controller
# Look for Account # Look for Account
foreach (Account::Search($request->input('term')) foreach (Account::Search($request->input('term'))
->whereIN('id',$uo->all_clients()->pluck('id')) ->whereIN('id',$uo->all_accounts()->pluck('id'))
->orderBy('company') ->orderBy('company')
->orderBy('last_name') ->orderBy('last_name')
->orderBy('first_name') ->orderBy('first_name')
@ -35,6 +33,15 @@ class SearchController extends Controller
$result->push(['label'=>sprintf('A:%s %s',$o->aid,$o->name),'value'=>'/u/account/'.$o->id]); $result->push(['label'=>sprintf('A:%s %s',$o->aid,$o->name),'value'=>'/u/account/'.$o->id]);
} }
# Look for an ADSL/NBN Service
foreach (Adsl::Search($request->input('term'))
->whereIN('account_id',$uo->all_accounts()->pluck('id'))
->orderBy('service_number')
->limit(10)->get() as $o)
{
$result->push(['label'=>sprintf('S:%s (%s)',$o->name,$o->service->sid),'value'=>'/u/service/'.$o->id]);
}
return $result; return $result;
} }
} }

View File

@ -57,6 +57,14 @@ class UserHomeController extends Controller
public function service(Service $o) public function service(Service $o)
{ {
foreach ([
sprintf('u.service.%s.%s',$o->type->type,$o->status),
sprintf('u.service.%s',$o->status),
] as $v)
if (view()->exists($v))
return View($v,['o'=>$o]);
// View doesnt exist, fall back to default view
return View('u.service',['o'=>$o]); return View('u.service',['o'=>$o]);
} }

View File

@ -75,7 +75,8 @@ class Account extends Model
/** /**
* Search for a record * Search for a record
* *
* @param User $uo * @param $query
* @param string $term
* @return * @return
*/ */
public function scopeSearch($query,string $term) public function scopeSearch($query,string $term)

View File

@ -4,6 +4,8 @@ namespace App\Models\Base;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use App\Models\Service;
abstract class ServiceType extends Model abstract class ServiceType extends Model
{ {
public $timestamps = FALSE; public $timestamps = FALSE;
@ -17,4 +19,28 @@ abstract class ServiceType extends Model
{ {
return $this->morphOne(Service::class,'type','model','id'); return $this->morphOne(Service::class,'type','model','id');
} }
/** SCOPES */
/**
* Search for a record
*
* @param $query
* @param string $term
* @return
*/
public function scopeSearch($query,string $term)
{
return $query
->with(['service'])
->join('ab_service','ab_service.id','=',$this->getTable().'.service_id')
->Where('ab_service.id','like','%'.$term.'%');
}
/** ATTRIBUTES **/
public function getTypeAttribute()
{
return strtolower((new \ReflectionClass($this))->getShortName());
}
} }

View File

@ -57,7 +57,7 @@ class Product extends Model
private function getDefaultLanguage() private function getDefaultLanguage()
{ {
return config('SITE_SETUP')->language; return config('SITE_SETUP')->language();
} }
public function getDescriptionAttribute() public function getDescriptionAttribute()

View File

@ -2,6 +2,8 @@
namespace App\Models; namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
@ -95,6 +97,37 @@ class Service extends Model
return $this->belongsTo(Account::class); return $this->belongsTo(Account::class);
} }
public function invoice_items($active=TRUE)
{
$query = $this->hasMany(InvoiceItem::class)
->orderBy('date_orig');
if ($active)
$query->where('active','=',TRUE);
return $query;
}
/**
* Invoices for this service
*
*/
public function invoices($active=TRUE)
{
$query = $this->hasManyThrough(Invoice::class,InvoiceItem::class,NULL,'id',NULL,'invoice_id')
->distinct('id')
->where('ab_invoice.site_id','=',$this->site_id)
->where('ab_invoice_item.site_id','=',$this->site_id)
->orderBy('date_orig')
->orderBy('due_date');
if ($active)
$query->where('ab_invoice_item.active','=',TRUE)
->where('ab_invoice.active','=',TRUE);
return $query;
}
/** /**
* Account that ordered the service * Account that ordered the service
* *
@ -105,16 +138,6 @@ class Service extends Model
return $this->belongsTo(Account::class); return $this->belongsTo(Account::class);
} }
/**
* Tenant that the service belongs to
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function site()
{
return $this->belongsTo(Site::class);
}
/** /**
* Product of the service * Product of the service
* *
@ -125,6 +148,16 @@ class Service extends Model
return $this->belongsTo(Product::class); return $this->belongsTo(Product::class);
} }
/**
* Tenant that the service belongs to
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function site()
{
return $this->belongsTo(Site::class);
}
/** /**
* Return a child model with details of the service * Return a child model with details of the service
* *
@ -180,6 +213,25 @@ class Service extends Model
return $this->getUrlAdminAttribute(); return $this->getUrlAdminAttribute();
} }
public function getBillingPriceAttribute(): float
{
if ($this->price)
return $this->addtax($this->price);
dd($this->product->price_group,$this);
return $this->cost;
}
/**
* Return the service billing period
*
* @return string
*/
public function getBillingPeriodAttribute(): string
{
return Arr::get($this->product->PricePeriods(),$this->recur_schedule,'Unknown');
}
/** /**
* Date the service expires, also represents when it is paid up to * Date the service expires, also represents when it is paid up to
* *
@ -191,23 +243,24 @@ class Service extends Model
} }
/** /**
* Services Unique Identifier * Return the date for the next invoice
* *
* @return string * @todo This function negates the need for date_next_invoice
* @return null
*/ */
public function getSIDAttribute(): string public function getInvoiceNextAttribute(): Carbon
{ {
return sprintf('%02s-%04s.%05s',$this->site_id,$this->account_id,$this->id); $last = $this->getInvoiceToAttribute();
return $last ? $last->addDay() : now();
} }
/** /**
* Return the date for the next invoice * Get the date that the service has been invoiced to
*
* @return null
*/ */
public function getInvoiceNextAttribute() public function getInvoiceToAttribute()
{ {
return $this->date_next_invoice ? $this->date_next_invoice->format('Y-m-d') : NULL; return $this->invoice_items->count() ? $this->invoice_items->last()->date_stop : NULL;
} }
/** /**
@ -294,6 +347,16 @@ class Service extends Model
return $this->getSIDAttribute(); return $this->getSIDAttribute();
} }
/**
* Services Unique Identifier
*
* @return string
*/
public function getSIDAttribute(): string
{
return sprintf('%02s-%04s.%05s',$this->site_id,$this->account_id,$this->id);
}
/** /**
* Return the Service Status * Return the Service Status
* *
@ -319,6 +382,24 @@ class Service extends Model
: ''; : '';
} }
/**
* Return a HTML status box
*
* @return string
*/
public function getStatusHTMLAttribute(): string
{
$class = NULL;
switch ($this->status)
{
case 'ACTIVE':
$class = 'badge-success';
break;
}
return sprintf('<span class="badge %s">%s</span>',$class,$this->status);
}
/** /**
* URL used by an admin to administer the record * URL used by an admin to administer the record
* *
@ -353,6 +434,25 @@ class Service extends Model
/** FUNCTIONS **/ /** FUNCTIONS **/
/**
* Add applicable tax to the cost
*
* @todo This needs to be calculated, not fixed at 1.1
* @param float $value
* @return float
*/
public function addTax(float $value): float
{
return round($value*1.1,2);
}
public function invoices_due(): Collection
{
return $this->invoice_items->filter(function($item) {
return $item->invoice->due > 0;
});
}
/** /**
* Determine if a service is active. It is active, if active=1, or the order_status is not in inactive_status[] * Determine if a service is active. It is active, if active=1, or the order_status is not in inactive_status[]
* *

View File

@ -12,16 +12,45 @@ class Adsl extends \App\Models\Base\ServiceType
// @todo column service_id can be removed. // @todo column service_id can be removed.
protected $table = 'ab_service__adsl'; protected $table = 'ab_service__adsl';
protected $dates = ['service_connect_date','service_contract_date'];
/** SCOPES */
/**
* Search for a record
*
* @param $query
* @param string $term
* @return
*/
public function scopeSearch($query,string $term)
{
// Build our where clause
return parent::scopeSearch($query,$term)
->orwhere('service_number','like','%'.$term.'%')
->orWhere('service_address','like','%'.$term.'%')
->orWhere('ipaddress','like','%'.$term.'%');
}
/** ATTRIBUTES **/
/**
* @deprecated use getNameFullAttribute()
*/
public function getFullNameAttribute() public function getFullNameAttribute()
{ {
return ($this->service_number AND $this->service_address) return $this->getNameFullAttribute();
? sprintf('%s: %s',$this->service_number, $this->service_address)
: $this->name;
} }
public function getNameAttribute() public function getNameAttribute()
{ {
return $this->service_number ?: $this->service_address; return $this->service_number ?: $this->service_address;
} }
public function getNameFullAttribute()
{
return ($this->service_number AND $this->service_address)
? sprintf('%s: %s',$this->service_number, $this->service_address)
: $this->name;
}
} }

View File

@ -23,6 +23,9 @@ class Site extends Model
public function language() public function language()
{ {
if (! $this->id)
return Language::find(1);
return $this->belongsTo(Language::class); return $this->belongsTo(Language::class);
} }

View File

@ -225,6 +225,9 @@ class User extends Authenticatable
$result->push($o->accounts->where('active',TRUE)); $result->push($o->accounts->where('active',TRUE));
} }
// Include my accounts
$result->push($this->accounts);
return $result->flatten(); return $result->flatten();
} }

View File

@ -0,0 +1,46 @@
<table class="table table-bordered w-100" id="invoices">
<thead>
<tr>
<th class="text-right">#</th>
<th class="text-right">Issued</th>
<th class="text-right">Due</th>
<th class="text-right">Total</th>
<th class="text-right">Payments</th>
<th class="text-right">Outstanding</th>
</tr>
</thead>
<tbody>
@foreach ($o->invoices as $io)
<tr>
<td class="text-right"><a href="{{ url('u/invoice',$io->id) }}">{{ $io->id }}</a></td>
<td class="text-right">{{ $io->date_orig->format('Y-m-d') }}</td>
<td class="text-right">{{ $io->due_date->format('Y-m-d') }}</td>
<td class="text-right">${{ number_format($io->total,2) }}</td>
<td class="text-right">${{ number_format($io->paid,2) }}</td>
<td class="text-right">${{ number_format($io->due,2) }}</td>
</tr>
@endforeach
</tbody>
</table>
@section('page-scripts')
@css('https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css','jq-dt-css','jquery');
@js('https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js','jq-dt-js','jquery');
@css('https://cdn.datatables.net/responsive/2.2.1/css/responsive.dataTables.min.css','jq-dt-r-css','jq-dt-css')
@js('https://cdn.datatables.net/responsive/2.2.1/js/dataTables.responsive.min.js','jq-dt-r-js','jq-dt-js')
@css('/plugin/dataTables/dataTables.bootstrap4.css','dt-bootstrap4-css','jq-dt-css')
@js('/plugin/dataTables/dataTables.bootstrap4.js','dt-bootstrap4-js','jq-dt-js')
<script type="text/javascript">
$(document).ready(function() {
$('#invoices').DataTable( {
responsive: true,
order: [1, 'desc']
});
$('#invoices tbody').on('click','tr', function () {
$(this).toggleClass('selected');
});
});
</script>
@append

View File

@ -0,0 +1,38 @@
<div class="card card-primary card-outline">
<div class="card-header">
<strong>Service Information</strong>
</div>
<div class="card-body">
<table class="table table-borderless">
<tr>
<th>Account</th>
<td>{{ $o->account->name }}</td>
</tr>
<tr>
<th>Active</th>
<td>{!! $o->status_html !!}</td>
</tr>
<tr>
<th>Billing Period</th>
<td>{{ $o->billing_period }}</td>
</tr>
<tr>
<th>Billing Amount</th>
<td>${{ number_format($o->billing_price,2) }}</td>
</tr>
<tr>
<th>Invoiced To</th>
<td>{{ $o->invoice_to ? $o->invoice_to->format('Y-m-d') : '' }}</td>
</tr>
<tr>
<th>Next Invoice</th>
<td>{{ $o->invoice_next ? $o->invoice_next->format('Y-m-d') : '' }}</td>
</tr>
<tr>
<th>Current Invoices Due</th>
<td>${{ number_format($o->invoices_due()->sum('due'),2) }} <small>({{ $o->invoices_due()->count() }})</small></td>
</tr>
</table>
</div>
</div>

View File

@ -5,7 +5,7 @@
@endsection @endsection
@section('contentheader_title') @section('contentheader_title')
Service # Service #{{ $o->id }}
@endsection @endsection
@section('contentheader_description') @section('contentheader_description')
{{ $o->service_id }} {{ $o->service_id }}
@ -14,11 +14,11 @@
@section('main-content') @section('main-content')
<div class="col-md-12"> <div class="col-md-12">
<div class="box box-primary"> <div class="box box-primary">
<div class="box-header with-border"> <div class="card-header with-border">
<h3 class="box-title">Service Information</h3> <h3 class="card-title">Service Information</h3>
</div> </div>
<div class="box-body"> <div class="card-body">
<div class="col-md-3"> <div class="col-md-3">
@switch($o->order_status) @switch($o->order_status)
@case('ORDER-SUBMIT') @case('ORDER-SUBMIT')

View File

@ -0,0 +1,129 @@
@extends('adminlte::layouts.app')
@section('htmlheader_title')
{{ $o->SID }}
@endsection
@section('contentheader_title')
Service: {{ $o->SID }}
@endsection
@section('page_title')
{{ $o->SID }}
@endsection
@section('contentheader_description')
{{ $o->type->name_full }}
@endsection
@section('main-content')
<div class="content">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header p-2">
<ul class="nav nav-pills">
<li class="nav-item"><a class="nav-link active" href="#tab-service" data-toggle="tab">Service</a></li>
<li class="nav-item"><a class="nav-link" href="#tab-invoice" data-toggle="tab">Invoices</a></li>
<li class="nav-item"><a class="nav-link" href="#tab-email" data-toggle="tab">Emails</a></li>
<li class="nav-item"><a class="nav-link" href="#tab-traffic" data-toggle="tab">Traffic</a></li>
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<div class="active tab-pane" id="tab-service">
<div class="row">
<div class="card-group w-100">
@include('common.service.widget.info')
<div class="card card-primary card-outline">
<div class="card-header">
<strong>Service Details</strong>
</div>
<div class="card-body">
<table class="table table-borderless">
<tr>
<th>Number</th>
<td>{{ $o->type->service_number }}</td>
</tr>
<tr>
<th>Address</th>
<td>{{ $o->type->service_address }}</td>
</tr>
<tr>
<th>Connected</th>
<td>{{ $o->type->service_connect_date->format('Y-m-d') }}</td>
</tr>
<tr>
<th>Contract</th>
<td>{{ $o->type->contract_term }} mths <small>(until {{ $o->type->service_contract_date ? $o->type->service_contract_date->addMonths($o->type->contract_term)->format('Y-m-d') : 'N/A' }})</small></td>
</tr>
<tr>
<th>Username</th>
<td>{{ $o->type->service_username }}</td>
</tr>
<tr>
<th>Password</th>
<td>{{ $o->type->service_password }}</td>
</tr>
<tr>
<th>IP Address</th>
<td>{{ $o->type->ipaddress ? $o->type->ipaddress : 'Dynamic' }}</td>
</tr>
</table>
</div>
</div>
<div class="card card-primary card-outline">
<div class="card-header">
<strong>DSL Details</strong>
</div>
<div class="card-body">
<table class="table table-borderless">
<tr>
<th>Speed</th>
<td>TBA</td>
</tr>
<tr>
<th>Peak Included Downloads</th>
<td>TBA</td>
</tr>
<tr>
<th>Traffic Last Month</th>
<td>TBA</td>
</tr>
<tr>
<th>Traffic This Month</th>
<td>TBA</td>
</tr>
<tr>
<th>Excess Traffic Charges to Bill</th>
<td>TBA</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="tab-invoice">
@include('common.invoice.widget.list')
</div>
<div class="tab-pane" id="tab-email">
</div>
<div class="tab-pane" id="tab-traffic">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -15,7 +15,10 @@
<th>Account</th><td>{{ $o->account->company }}</td> <th>Account</th><td>{{ $o->account->company }}</td>
</tr> </tr>
<tr> <tr>
<th>Active</th><td>{{ $o->active }} ({{ $o->order_status }})</td> <th>Active</th><td>{{ $o->active }} ({{ $o->order_status }}) [{{ $o->status }}]</td>
</tr>
<tr>
<th>Type</th><td>{{ $o->type->type }}</td>
</tr> </tr>
<tr> <tr>
<th>Product</th><td>{{ $o->product->name }}: {{ $o->name }}</td> <th>Product</th><td>{{ $o->product->name }}: {{ $o->name }}</td>