Work on Service rendering

This commit is contained in:
Deon George 2020-02-05 13:47:24 +09:00
parent 01a7d73c17
commit 5f5d114f42
12 changed files with 412 additions and 313 deletions

View File

@ -3,10 +3,11 @@
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
use Barryvdh\Snappy\Facades\SnappyPdf as PDF;
use App\Models\{Invoice,Service};
use App\User;
use PDF;
class UserHomeController extends Controller
{
@ -15,7 +16,12 @@ class UserHomeController extends Controller
$this->middleware('auth');
}
public function home()
/**
* Logged in users home page
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function home(): View
{
switch (Auth::user()->role()) {
case 'customer':
@ -32,11 +38,23 @@ class UserHomeController extends Controller
}
}
public function invoice(Invoice $o)
/**
* Render a specific invoice for the user
*
* @param Invoice $o
* @return View
*/
public function invoice(Invoice $o): View
{
return View('u.invoice',['o'=>$o]);
}
/**
* Return the invoice in PDF format, ready to download
*
* @param Invoice $o
* @return mixed
*/
public function invoice_pdf(Invoice $o)
{
return PDF::loadView('u.invoice', ['o'=>$o])->stream(sprintf('%s.pdf',$o->invoice_account_id));
@ -56,8 +74,15 @@ class UserHomeController extends Controller
abort(307,sprintf('http://www.graytech.net.au/u/%s/%s/%s',$type,$action,$id));
}
public function service(Service $o)
/**
* Return details on the users service
*
* @param Service $o
* @return View
*/
public function service(Service $o): View
{
return View('u.service',['o'=>$o]);
foreach ([
sprintf('u.service.%s.%s',$o->type->type,$o->status),
sprintf('u.service.%s',$o->status),

View File

@ -0,0 +1,20 @@
<?php
namespace App\Interfaces;
interface ServiceItem
{
/**
* Return the Service Description.
*
* @return string
*/
public function getServiceDescriptionAttribute():string;
/**
* Return the Service Name.
*
* @return string
*/
public function getServiceNameAttribute():string;
}

View File

@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Model;
use App\Traits\OrderServiceOptions;
class AccoutBilling extends Model
class AccountBilling extends Model
{
protected $table = 'ab_account_billing';
public $timestamps = FALSE;

View File

@ -4,6 +4,10 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Arr;
use App\Traits\NextKey;
@ -11,17 +15,18 @@ use App\Traits\NextKey;
class Service extends Model
{
use NextKey;
const RECORD_ID = 'service';
public $incrementing = FALSE;
const CREATED_AT = 'date_orig';
const UPDATED_AT = 'date_last';
public $incrementing = FALSE;
protected $table = 'ab_service';
protected $dates = ['date_last_invoice','date_next_invoice'];
protected $dates = [
'date_last_invoice',
'date_next_invoice'
];
public $dateFormat = 'U';
protected $with = ['product.descriptions','account.language','type'];
protected $table = 'ab_service';
protected $casts = [
'order_info'=>'array',
@ -91,17 +96,27 @@ class Service extends Model
/**
* Account the service belongs to
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
* @return BelongsTo
*/
public function account()
{
return $this->belongsTo(Account::class);
}
/**
* Return automatic billing details
*
* @return HasOne
*/
public function billing()
{
return $this->hasOne(AccountBilling::class);
}
/**
* Return Charges associated with this Service
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
* @return HasMany
*/
public function charges()
{
@ -146,10 +161,9 @@ class Service extends Model
/**
* Account that ordered the service
*
* @todo changed to orderedby
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
* @return BelongsTo
*/
public function orderby()
public function orderedby()
{
return $this->belongsTo(Account::class);
}
@ -157,30 +171,23 @@ class Service extends Model
/**
* Product of the service
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
* @return BelongsTo
*/
public function product()
{
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 \Illuminate\Database\Eloquent\Relations\MorphTo
* @return MorphTo
*/
public function type()
{
if (! $this->model)
abort(500,'Missing Model in',['service'=>$this]);
return $this->morphTo(null,'model','id','service_id');
}
@ -192,7 +199,8 @@ class Service extends Model
public function scopeActive($query)
{
return $query->where(function () use ($query) {
$query->where('active',TRUE)->orWhereNotIn('order_status',$this->inactive_status);
$query->where('active',TRUE)
->orWhereNotIn('order_status',$this->inactive_status);
});
}
@ -205,7 +213,8 @@ class Service extends Model
public function scopeInActive($query)
{
return $query->where(function () use ($query) {
$query->where('active',FALSE)->orWhereIn('order_status',$this->inactive_status);
$query->where('active',FALSE)
->orWhereIn('order_status',$this->inactive_status);
});
}
@ -251,6 +260,16 @@ class Service extends Model
return $this->getUrlAdminAttribute();
}
/**
* Return the auto billing details
*
* @return mixed
*/
public function getAutoPayAttribute()
{
return $this->billing;
}
public function getBillingPriceAttribute(): float
{
// @todo Temporary for services that dont have recur_schedule set.
@ -337,6 +356,7 @@ class Service extends Model
* EG:
* For ADSL, this would be the phone number,
* For Hosting, this would be the domain name, etc
* @deprecated
*/
public function getNameShortAttribute()
{
@ -372,6 +392,25 @@ class Service extends Model
return $result;
}
/**
* Work out when this service has been paid to.
*
* @todo This might need to be optimised
*/
public function getPaidToAttribute()
{
foreach ($this->invoices->reverse() as $o) {
if ($o->due == 0) {
return $o->items
->filter(function($item) {
return $item->item_type === 0;
})
->last()
->date_stop;
}
}
}
/**
* Get the Product's Category for this service
*
@ -392,9 +431,9 @@ class Service extends Model
}
/**
* @deprecated see getServiceIdAttribute()
* @deprecated see getSIDAttribute()
*/
public function getServiceIdAttribute()
public function getServiceIdAttribute(): string
{
return $this->getSIDAttribute();
}
@ -425,6 +464,50 @@ class Service extends Model
return sprintf('%02s-%04s.%05s',$this->site_id,$this->account_id,$this->id);
}
/**
* Return the service description.
* For:
* + Broadband, this is the service address
* + Domains, blank
* + Hosting, blank
* + SSL, blank
*
* @return string
*/
public function getSDescAttribute(): string
{
return $this->type->service_description;
}
/**
* 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
*
* @return string
*/
public function getSNameAttribute(): string
{
return $this->type->service_name;
}
/**
* Return the service product type
* This is used for view specific details
*
* @return string
*/
public function getSTypeAttribute(): string
{
switch($this->product->model) {
case 'App\Models\Product\Adsl': return 'broadband';
default: abort(500,'Product type not configured',['product'=>$this->product]);
}
}
/**
* Return the Service Status
*
@ -545,6 +628,16 @@ class Service extends Model
return (! $this->external_billing) AND (! $this->suspend_billing) AND $this->getInvoiceNextAttribute()->lessThan(now()->addDays($days));
}
/**
* Identify if a service is being ordered
*
* @return bool
*/
public function isPending(): bool
{
return ! $this->active AND ! in_array($this->order_status,$this->inactive_status);
}
public function next_invoice_items(): \Illuminate\Support\Collection
{
$result = collect();

View File

@ -2,17 +2,35 @@
namespace App\Models\Service;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphOne;
use App\Interfaces\ServiceItem;
use App\Models\Base\ServiceType;
use App\Models\Service;
use App\Traits\NextKey;
class Adsl extends \App\Models\Base\ServiceType
class Adsl extends ServiceType implements ServiceItem
{
use NextKey;
const RECORD_ID = 'service__adsl';
// @todo column service_id can be removed.
protected $table = 'ab_service__adsl';
protected $dates = ['service_connect_date','service_contract_date'];
protected $dates = [
'service_connect_date',
'service_contract_date'
];
/**
* The service this belongs to
*
* @return BelongsTo|MorphOne
*/
public function service()
{
return $this->belongsTo(Service::class);
}
/** SCOPES */
@ -35,22 +53,41 @@ class Adsl extends \App\Models\Base\ServiceType
/** ATTRIBUTES **/
/**
* @deprecated use getNameFullAttribute()
* @deprecated use $o->type()->service_name;
* @return mixed|string
*/
public function getFullNameAttribute()
{
return $this->getNameFullAttribute();
}
public function getNameAttribute()
{
return $this->service_number ?: $this->service_address;
}
public function getNameFullAttribute()
/**
* Return the service address
*
* @return string
*/
public function getServiceDescriptionAttribute(): string
{
return ($this->service_number AND $this->service_address)
? sprintf('%s: %s',$this->service_number, $this->service_address)
: $this->name;
return $this->service_address ?: 'NO Service Address';
}
/**
* Return the service number
*
* @return string
*/
public function getServiceNameAttribute(): string
{
return $this->service_number ?: 'NO Service Number';
}
/**
* Is this service currently in a contract
*
* @return bool
*/
public function inContract(): bool
{
return $this->service_contract_date AND $this->service_contract_date->addMonths($this->contract_term)->isFuture();
}
}

View File

@ -3,11 +3,11 @@
@section('htmlheader_title')
Reseller Home
@endsection
@section('contentheader_title')
@section('page_title')
{{ $o->full_name }}
@endsection
@section('page_title')
@section('contentheader_title')
{{ $o->full_name }}
@endsection
@section('contentheader_description')
@ -59,9 +59,6 @@
<div class="col-xs-6">
@include('r.agents')
</div>
<div class="col-xs-6">
@include('r.service_inactive')
</div>
<div class="col-xs-6">
@include('r.clients')
</div>

View File

@ -1,87 +0,0 @@
<div class="box box-warning">
<div class="box-header">
<h3 class="box-title">Services Inactive</h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse" data-toggle="tooltip" title="Collapse">
<i class="fa fa-minus"></i></button>
<button type="button" class="btn btn-box-tool" data-widget="remove" data-toggle="tooltip" title="Remove">
<i class="fa fa-times"></i></button>
</div>
</div>
<div class="box-body">
@if ($user->all_client_service_inactive()->count())
<table class="table table-bordered table-striped table-hover" id="service_inactive" style="width: 100%;">
<thead>
<tr>
<th>ID</th>
<th>Account</th>
<th>Name</th>
<th>Status</th>
<th>Product</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Count {{ $user->all_client_service_inactive()->count() }}</th>
<th colspan="4">&nbsp;</th>
</tr>
</tfoot>
</table>
@else
<p>No Inactive Services</p>
@endif
</div>
</div>
@section('page-scripts')
@css('https://cdn.datatables.net/responsive/2.2.1/css/responsive.dataTables.min.css')
@css('https://cdn.datatables.net/rowgroup/1.0.2/css/rowGroup.dataTables.min.css')
@js('https://cdn.datatables.net/responsive/2.2.1/js/dataTables.responsive.min.js')
@js('https://cdn.datatables.net/rowgroup/1.0.2/js/dataTables.rowGroup.min.js')
<style>
table.dataTable td {
outline: none;
}
</style>
<script type="text/javascript">
$(document).ready(function() {
$('#service_inactive').DataTable( {
responsive: true,
ajax: {
url: "/api/r/service_inactive"
},
columns: [
{ data:
@if($user->isWholesaler())
"admin_service_id_url"
@else
"service_id_url"
@endif
},
{ data: "account_name" },
{ data: "name_full" },
{ data: "status" },
{ data: "product_name" }
],
language: {
emptyTable: "No InActive Services"
},
order: [3, 'asc'],
rowGroup: {
dataSrc: 'account_name',
startRender: null,
endRender: function ( rows, group ) {
return rows.count()+' x ' + group;
},
},
orderFixed: [1, 'asc']
});
$('#service_movements tbody').on('click','tr', function () {
$(this).toggleClass('selected');
});
});
</script>
@append

View File

@ -3,11 +3,11 @@
@section('htmlheader_title')
Client Home
@endsection
@section('contentheader_title')
@section('page_title')
{{ $o->full_name }}
@endsection
@section('page_title')
@section('contentheader_title')
{{ $o->full_name }}
@endsection
@section('contentheader_description')

View File

@ -1,38 +1,95 @@
@extends('adminlte::layouts.app')
@section('htmlheader_title')
Service #{{ $o->id }}
{{ $o->sid }}
@endsection
@section('page_title')
{{ $o->sid }}
@endsection
@section('contentheader_title')
Service #{{ $o->id }}
Service: {{ $o->sid }} <strong>NBN-50/20-100</strong>
@endsection
@section('contentheader_description')
{{ $o->service_id }}
{{ $o->sname }}: {{ $o->sdesc }}
@endsection
@section('main-content')
<div class="col-md-12">
<div class="box box-primary">
<div class="card-header with-border">
<h3 class="card-title">Service Information</h3>
<div class="row">
<!-- Service Details -->
<div class="col-5">
@include('u.service.widgets.'.$o->stype.'.details',['o'=>$o->type])
@include('u.service.widgets.information')
</div>
<div class="col-7">
<div class="card">
<div class="card-header bg-dark d-flex p-0">
<span class="p-3"><i class="fa fa-bars"></i></span>
<ul class="nav nav-pills p-2">
<li class="nav-item"><a class="nav-link active" href="#product" data-toggle="tab">Product</a></li>
<li class="nav-item"><a class="nav-link" href="#traffic" data-toggle="tab">Traffic</a></li>
<li class="nav-item"><a class="nav-link" href="#invoice_next" data-toggle="tab">Next Invoice</a></li>
<li class="nav-item"><a class="nav-link" href="#invoices" data-toggle="tab">Invoices</a></li>
<li class="nav-item"><a class="nav-link" href="#emails" data-toggle="tab">Emails</a></li>
</ul>
<ul class="nav nav-pills ml-auto p-2">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#">
Dropdown <span class="caret"></span>
</a>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" tabindex="-1" href="#tab_3">Action</a>
<a class="dropdown-item" tabindex="-1" href="#">Another action</a>
<a class="dropdown-item" tabindex="-1" href="#">Something else here</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" tabindex="-1" href="#">Separated link</a>
</div>
</li>
</ul>
</div><!-- /.card-header -->
<div class="card-body">
<div class="col-md-3">
@switch($o->order_status)
@case('ORDER-SUBMIT')
@case('ORDER-SENT')
@case('ORDER-HOLD')
@case('ORDERED')
@include('u.widgets.service.order.sent')
@break
@default
@include('u.widgets.service.info')
@endswitch
<div class="tab-content">
<div class="tab-pane fade" id="traffic" role="tabpanel">
Traffic.
</div>
<div class="tab-pane fade show active" id="product" role="tabpanel">
Product.
</div>
<div class="tab-pane fade" id="invoice_next" role="tabpanel">
Invoice Next.
</div>
<div class="tab-pane fade" id="invoices" role="tabpanel">
Invoices.
</div>
<div class="tab-pane fade" id="emails" role="tabpanel">
Email.
</div>
</div>
</div>
<!-- /.card -->
</div>
</div>
</div>
@endsection
@section('page-scripts')
<style>
.nav-pills .nav-link.active, .nav-pills .show>.nav-link {
background-color: #ffffff;
color: #343a40;
}
.nav-pills .nav-link:hover {
background-color: #6c757d;
color: #ffffff;
}
.nav-pills .nav-link:not(.active):hover {
background-color: #6c757d;
color: #ffffff;
}
</style>
@append

View File

@ -1,152 +0,0 @@
@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>{{ $o->product->type->speed }}</td>
</tr>
@if ($o->product->type->base_down_peak)
<tr>
<th>Peak Included Downloads</th>
<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>
</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">
<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">
</div>
<div class="tab-pane" id="tab-traffic">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,70 @@
<div class="card">
<!-- @todo -->
@if($o->service->isPending())
<div class="ribbon-wrapper ribbon-lg">
<div class="ribbon bg-warning">
Pending
</div>
</div>
@endif
<div class="card-header bg-gray-dark">
<h3 class="card-title">Broadband Details</h3>
</div>
<div class="card-body bg-gray-dark">
<table class="table table-sm">
<tr>
<th>Address</th>
<td class="text-uppercase">{{ $o->service_description }}</td>
</tr>
<tr>
<th>Service Number</th>
<td>{{ $o->service_name }}</td>
</tr>
<tr>
<th>Service Username</th>
<td>{{ $o->service_username }}</td>
</tr>
<tr>
<th>Service Password</th>
<td>{{ $o->service_password }}</td>
</tr>
@if($o->service_connect_date)
<tr>
<th>Connected</th>
<td>{{ $o->service_connect_date->format('Y-m-d') }}</td>
</tr>
@endif
<!-- @todo -->
@if(FALSE)
<tr>
<th>Speed</th>
<td>{{ 'xxx/YY' }} Mbps</td>
</tr>
<tr>
<th>Traffic</th>
<td>{{ 'xxx' }} GB (YY GB used month)</td>
</tr>
@endif
<tr>
<th>IP Address</th>
<td>{{ $o->ipaddress ?: 'Dynamic' }}</td>
</tr>
@if ($o->inContract())
<tr>
<th>Contract</th>
<td>{{ $o->contract_term }} months <small>({{ ($x=$o->service_contract_date->addMonths($o->contract_term))->diffForHumans() }})</small></td>
</tr>
<tr>
<th>Contract End</th>
<td>{{ $x->format('Y-m-d') }}</td>
</tr>
@endif
<tr>
<th>Cancel Notice</th>
<td>1 month @if($o->inContract())<small>(after {{ $o->service_contract_date->addMonths($o->contract_term-1)->format('Y-m-d') }})</small>@endif</td>
</tr>
</table>
</div>
</div>

View File

@ -0,0 +1,39 @@
<div class="card">
<div class="card-header bg-light">
<h3 class="card-title">Service Information</h3>
</div>
<div class="card-body bg-light">
<table class="table table-sm">
<tr>
<th>Status</th>
<td>{{ $o->status }}</td>
</tr>
@if ($o->active)
<tr>
<th>Billed</th>
<td>{{ $o->billing_period }}</td>
</tr>
<tr>
<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>
<tr>
<th>Next Invoice</th>
<td>{{ $o->invoice_next->format('Y-m-d') }}</td>
</tr>
<tr>
<th>Estimated Invoice</th>
<td>${{ number_format($o->billing_price,2) }}</td>
</tr>
@endif
<tr>
<th>Payment Method</th>
<td>@if ($o->autopay)Direct Debit @else Invoice @endif</td>
</tr>
</table>
</div>
</div>