Progress on order progress

This commit is contained in:
Deon George 2020-04-19 08:33:41 +10:00
parent e6f823da39
commit 6480f40b22
No known key found for this signature in database
GPG Key ID: 7670E8DC27415254
16 changed files with 505 additions and 224 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@
/storage/*.key /storage/*.key
/vendor /vendor
/.idea /.idea
.editorconfig
/.vscode /.vscode
/.vagrant /.vagrant
Homestead.json Homestead.json

View File

@ -17,55 +17,6 @@ class AdminHomeController extends Controller
return View('a.service',['o'=>$o]); return View('a.service',['o'=>$o]);
} }
public function service_update(Request $request, Service $o)
{
if (! $o->validStatus(strtolower($request->input('action'))))
return $this->service($o);
$action = strtolower($request->input('action'));
switch ($action)
{
case 'approve':
// Send an email to the supplier.
// @todo Change to address to suppliers email address.
Mail::to('help@graytech.net.au')
->queue((new OrderRequestApprove($o,$request->input('order_notes') ?: 'NONE'))->onQueue('high'));
// Send an email to the client.
// @todo Your order has been submitted to supplier.
// Update the service to "ORDER-SENT"
$o->nextStatus($action);
break;
case 'reject':
$o->order_info = array_merge($o->order_info ? $o->order_info : [],['reason'=>$request->input('notes')]);
// Send mail to user
Mail::to($o->orderby->email)->queue((new OrderRequestReject($o,$request->input('notes')))->onQueue('email'));
$o->nextStatus($action);
break;
case 'hold':
case 'release':
$o->nextStatus($action);
break;
case 'update_reference':
$o->order_info = array_merge($o->order_info ? $o->order_info : [],['order_reference'=>$request->input('notes')]);
$o->save();
// No action specified.
default:
return $this->service($o);
}
return redirect(url('/a/service',[$o->id]));
}
public function setup() public function setup()
{ {
return view('a.setup'); return view('a.setup');

View File

@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\View\View;
use App\Models\Service;
class ServiceController extends Controller
{
/**
* Update a service
*
* @note: Route Middleware protects this path
* @param Service $o
* @return View|void
*/
public function update(Request $request,Service $o)
{
switch ($o->order_status) {
case 'ORDER-SENT':
if ($request->post()) {
foreach (['reference','notes'] as $key) {
$o->setOrderInfo($key,$request->post($key));
}
$o->save();
foreach ($request->post($o->stype) as $k=>$v) {
$o->type->{$k} = $v;
}
$o->type->save();
return redirect()->to(url('u/service',$o->id))->with('updated','Order sent notes updated.');
}
return $this->update_order_status($o);
default:
abort(499,'Not yet implemented');
}
}
private function update_order_status(Service $o)
{
return View('r.service.order.sent',['o'=>$o]);
}
}

View File

@ -87,6 +87,19 @@ class UserHomeController extends Controller
*/ */
public function service(Service $o): View public function service(Service $o): View
{ {
return View('u.service',['o'=>$o]); return View('u.service.home',['o'=>$o]);
}
/**
* Progress the order to the next stage
*
* @note: Route Middleware protects this path
* @param Service $o
* @param string $status
* @return \Illuminate\Http\RedirectResponse
*/
public function service_progress(Service $o,string $status)
{
return redirect()->to($o->action($status) ?: url('u/service',$o->id));
} }
} }

View File

@ -23,10 +23,10 @@ class ServicePolicy
// If this is a service for an account managed by a user. // If this is a service for an account managed by a user.
return ($user->services->pluck('id')->search($o->id)) return ($user->services->pluck('id')->search($o->id))
// The user is the wholesaler // The user is the wholesaler
OR $user->isWholesaler() OR $user->isWholesaler()
// The user is the reseller // The user is the reseller
OR $user->all_accounts()->pluck('id')->search($o->account_id); OR $user->all_accounts()->pluck('id')->search($o->account_id);
} }
@ -41,6 +41,18 @@ class ServicePolicy
return TRUE; return TRUE;
} }
/**
* Can the user progress an order status
*
* @param User $user
* @param Service $o
* @return bool
*/
public function progress(User $user, Service $o,string $next)
{
return $o->actions()->has($next);
}
/** /**
* Determine whether the user can update the service. * Determine whether the user can update the service.
* *

View File

@ -11,9 +11,12 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpKernel\Exception\HttpException;
use App\Traits\NextKey;
use Leenooks\Carbon; use Leenooks\Carbon;
use App\User;
use App\Traits\NextKey;
class Service extends Model class Service extends Model
{ {
@ -45,7 +48,7 @@ class Service extends Model
]; ];
protected $casts = [ protected $casts = [
'order_info'=>'array', 'order_info'=>'collection',
]; ];
public $dateFormat = 'U'; public $dateFormat = 'U';
@ -92,9 +95,58 @@ class Service extends Model
* *
* @var array * @var array
*/ */
private $valid_status = [ public static $action_progress = [
// Order Submitted // Order Submitted
'ORDER-SUBMIT' => ['approve'=>'ORDER-SENT','hold'=>'ORDER-HOLD','reject'=>'ORDER-REJECTED','cancel'=>'ORDER-CANCELLED'], 'ORDER-SUBMIT' => [
'fail'=>FALSE,
// Progress to next stages by who
'next'=>[
'ORDER-ACCEPT'=>['customer'],
'SETUP-PAYMENT-WAIT'=>['reseller','wholesaler']
],
// Manual or System moves to the next stage
'system'=>TRUE,
'method'=>'action_order_submit',
'title'=>'Order Submit',
],
// Client accepts order, if performed by RW
'ORDER-ACCEPT' => [
'fail'=>FALSE,
'next'=>[
'SETUP-PAYMENT-WAIT'=>['customer'],
],
'system'=>FALSE,
'method'=>'action_order_accept',
'title'=>'Client Accept Order',
],
// If the product has a setup, collect payment information
'SETUP-PAYMENT-WAIT' => [
'fail'=>FALSE,
'next'=>[
'PAYMENT-WAIT'=>['customer'],
],
'system'=>FALSE,
'method'=>'action_setup_payment_wait',
'title'=>'Setup Payment',
],
'PAYMENT-WAIT' => [
'fail'=>FALSE,
'next'=>[
'PAYMENT-CHECK'=>['reseller','wholesaler'],
],
'system'=>FALSE,
'method'=>'action_payment_wait',
'title'=>'Service Payment',
],
'PAYMENT-CHECK' => [
'fail'=>'ORDER-HOLD',
'next'=>[
'ORDER-SENT'=>[],
],
'system'=>TRUE,
'method'=>'action_payment_check',
'title'=>'Validate Payment Method',
],
// Order On Hold (Reason) // Order On Hold (Reason)
'ORDER-HOLD' => ['release'=>'ORDER-SUBMIT','update_reference'=>'ORDER-SENT'], 'ORDER-HOLD' => ['release'=>'ORDER-SUBMIT','update_reference'=>'ORDER-SENT'],
// Order Rejected (Reason) // Order Rejected (Reason)
@ -102,7 +154,15 @@ class Service extends Model
// Order Cancelled // Order Cancelled
'ORDER-CANCELLED' => [], 'ORDER-CANCELLED' => [],
// Order Sent to Supplier // Order Sent to Supplier
'ORDER-SENT' => ['update_reference'=>'ORDER-SENT','confirm'=>'ORDERED'], 'ORDER-SENT' => [
'fail'=>'ORDER-HOLD',
'next'=>[
'ORDERED'=>['wholesaler'],
],
'system'=>FALSE,
'method'=>'action_order_sent',
'title'=>'Send Order',
],
// Order Confirmed by Supplier // Order Confirmed by Supplier
'ORDERED' => ['update_reference'=>'ORDER-SENT'], 'ORDERED' => ['update_reference'=>'ORDER-SENT'],
]; ];
@ -493,7 +553,7 @@ class Service extends Model
*/ */
public function getNameShortAttribute() public function getNameShortAttribute()
{ {
return $this->type ? $this->type->service_name : $this->id; return $this->type ? $this->type->name : $this->id;
} }
/** /**
@ -504,25 +564,14 @@ class Service extends Model
return $this->getInvoiceNextAttribute(); return $this->getInvoiceNextAttribute();
} }
/** public function getOrderInfoNotesAttribute(): ?string
* This function will present the Order Info Details
*/
public function getOrderInfoDetailsAttribute(): string
{ {
if (! $this->order_info) return $this->getOrderInfoValue('notes');
return ''; }
$result = ''; public function getOrderInfoReferenceAttribute(): ?string
{
foreach ($this->order_info as $k=>$v) return $this->getOrderInfoValue('reference');
{
if (in_array($k,['order_reference']))
continue;
$result .= sprintf('%s: <b>%s</b><br>',ucfirst($k),$v);
}
return $result;
} }
/** /**
@ -738,6 +787,218 @@ class Service extends Model
/** FUNCTIONS **/ /** FUNCTIONS **/
// The action methods will return: NULL for no progress|FALSE for a failed status|next stage name.
/**
* Process for an order when status ORDER-ACCEPT stage.
* This method should have the client confirm/accept the order, if it was placed by a reseller/wholesaler.
*
* @return bool
*/
private function action_order_accept(): ?bool
{
// @todo TO IMPLEMENT
return TRUE;
}
/**
* Action method when status ORDER_SENT
* This method redirects to a form, where updating the form will progress to the next stage.
*/
private function action_order_sent()
{
throw new HttpException(301,url('r/service/update',$this->id));
}
/**
* Action method when status ORDER_SUBMIT
*
* @return bool
*/
private function action_order_submit(): ?bool
{
// @todo TO IMPLEMENT
return TRUE;
}
/**
* Process for an order when status SETUP-PAYMENT-WAIT stage.
* This method should collect any setup fees payment.
*
* @return bool
*/
private function action_setup_payment_wait(): ?bool
{
// @todo TO IMPLEMENT
return TRUE;
}
/**
* Process for an order when status PAYMENT-CHECK stage.
* This method should validate any payment details.
*
* @return bool
*/
private function action_payment_check(): ?bool
{
// @todo TO IMPLEMENT
return TRUE;
}
/**
* Process for an order when status PAYMENT-WAIT stage.
* This method should collect any service payment details.
*
* @return bool
*/
private function action_payment_wait(): ?bool
{
// @todo TO IMPLEMENT
return TRUE;
}
private function getOrderInfoValue(string $key): ?string
{
return $this->order_info ? $this->order_info->get($key) : NULL;
}
/**
* Get the current stage parameters
*
* @param string $stage
* @return array
*/
private function getStageParameters(string $stage): Collection
{
$result = collect(Arr::get(self::$action_progress,$stage));
$myrole = array_search(Auth::user()->role(),User::$role_order);
// If we have no valid next stage, return an empty collection.
if (! $result->count() OR $myrole===FALSE)
return $result;
// Filter the result based on who we are
$next = collect();
foreach ($result->get('next') as $action=>$roles) {
// Can the current user do this role?
$cando = FALSE;
foreach ($roles as $role) {
if ($myrole < array_search($role,User::$role_order)) {
$cando = TRUE;
break;
}
}
//dd($action,$roles,$result);
if ($cando OR $result->get('system')) {
$next->put($action,$roles);
}
}
$result->put('next',$next);
return $result;
}
/**
* @notes
* + When progressing stages, we know who the user is that initiated the stage,
* + If no user, then we perform stages SYSTEM=TRUE
* + We need to validate that the current stage is complete, before progressing to the next stage
* + The current stage may require input from a user, or automation process to progress
* + Before leaving this method, we update the service with the stage that it is currently on.
*
* @param string $stage
* @return bool|int|string|null
*/
public function action(string $stage)
{
// While stage has a string value, that indicates the next stage we want to go to
// If stage is NULL, the current stage hasnt been completed
// If stage is FALSE, then the current stage failed, and may optionally be directed to another stage.
while ($stage) {
// Check that stage is a valid next action for the user currently performing it
$current = $this->getStageParameters($this->order_status);
$next = $this->getStageParameters($stage);
// If valid, call the method to confirm that the current stage is complete
if (method_exists($this,$current['method'])) {
try {
$result = $this->{$current['method']}();
// If we have a form to complete, we need to return with a URL, so we can catch that with an Exception
} catch (HttpException $e) {
if ($e->getStatusCode() == 301)
return ($e->getMessage());
}
// @todo Implement a status message
if (is_null($result)) {
$stage = NULL;
abort(500,'Current Method Cannot Proceed: '.$result);
// @todo Implement a status message
} elseif (! $result) {
$stage = NULL;
abort(500,'Current Method FAILED: '.$result);
} else {
$this->order_status = $stage;
$this->save();
// If we have more than 1 next step for the next stage, we'll have to end.
if ($this->actions()->count() > 1) {
$stage = NULL;
} else {
$stage = $this->actions()->keys()->first();
}
}
// @todo Implement a status message
} else {
// Cant do anything, dont have a method to check if we can leave
$stage = NULL;
abort(500,'NO Method Cannot Proceed to leave this stage: '.$next['method']);
}
// If valid, call the method to start the next stage
}
}
/**
* Work out the next applicable actions for this service status
*
* @notes
* + Clients can only progress 1 step, if they are in the next step.
* + Resellers/Wholesales can progress to the next Reseller/Wholesaler and any steps in between.
*
* @param bool $next Only show next actions
* @return Collection
*/
public function actions(): Collection
{
$result = collect();
$action = $this->getStageParameters($this->order_status);
if (! $action->count())
return $result;
// Next Action
foreach ($this->getStageParameters($this->order_status)->get('next') as $k=>$v) {
$result->put($k,Arr::get(self::$action_progress,$k.'.title'));
}
// No next actions, that will mean the current action hasnt completed.
if (! $result->count())
$result->put($this->order_status,Arr::get($action,'title'));
return $result;
}
/** /**
* Add applicable tax to the cost * Add applicable tax to the cost
* *
@ -802,7 +1063,7 @@ class Service extends Model
*/ */
public function next_invoice_items(bool $future): Collection public function next_invoice_items(bool $future): Collection
{ {
if ($this->wasCancelled() OR $this->suspend_billing OR ! $this->active) if ($this->wasCancelled() OR $this->suspend_billing OR $this->external_billing OR (! $future AND ! $this->active))
return collect(); return collect();
// If pending, add any connection charges // If pending, add any connection charges
@ -879,23 +1140,6 @@ class Service extends Model
return $this->invoice_items->filter(function($item) { return ! $item->exists; }); return $this->invoice_items->filter(function($item) { return ! $item->exists; });
} }
/**
* @todo
* @param string $status
* @return $this
*/
public function nextStatus(string $status) {
if ($x=$this->validStatus($status))
{
$this->order_status = $x;
$this->save();
return $this;
}
abort(500,'Next Status not set up for:'.$this->order_status);
}
/** /**
* This function will return the associated service model for the product type * This function will return the associated service model for the product type
* @deprecated use $this->type * @deprecated use $this->type
@ -919,22 +1163,17 @@ class Service extends Model
} }
/** /**
* Return if the proposed status is valid. * Store order info details
* *
* @param string $status * @param string $key
* @return string | NULL * @param string $value
*/ */
private function testNextStatusValid(string $status) public function setOrderInfo(string $key,string $value): void
{ {
return Arr::get(Arr::get($this->valid_status,$this->order_status,[]),$status,NULL); $x = is_null($this->order_info) ? collect() : $this->order_info;
} $x->put($key,$value);
/** $this->order_info = $x;
* @deprecated use testNextStatusValid()
*/
public function validStatus(string $status)
{
return $this->testNextStatusValid($status);
} }
/** /**

View File

@ -64,6 +64,16 @@ class User extends Authenticatable
protected $with = ['accounts']; protected $with = ['accounts'];
/**
* Role hierarchy order
* @var array
*/
public static $role_order = [
'wholesaler',
'reseller',
'customer',
];
/** /**
* The accounts that this user manages * The accounts that this user manages
* *
@ -452,7 +462,7 @@ class User extends Authenticatable
public function client_service_movements(): DatabaseCollection public function client_service_movements(): DatabaseCollection
{ {
return Service::active() return Service::active()
->select(['id','account_id','product_id','order_status']) ->select(['id','account_id','product_id','order_status','model'])
->where('order_status','!=','ACTIVE') ->where('order_status','!=','ACTIVE')
->whereIN('account_id',$this->all_accounts()->pluck('id')->unique()->toArray()) ->whereIN('account_id',$this->all_accounts()->pluck('id')->unique()->toArray())
->with(['account','product']) ->with(['account','product'])

View File

@ -3,7 +3,7 @@
return [ return [
'pdf' => array( 'pdf' => array(
'enabled' => true, 'enabled' => true,
'binary' => '/usr/bin/wkhtmltopdf', 'binary' => '/usr/local/bin/wkhtmltopdf',
'timeout' => false, 'timeout' => false,
'options' => array('print-media-type' => true), 'options' => array('print-media-type' => true),
'env' => array(), 'env' => array(),
@ -11,7 +11,7 @@ return [
'image' => array( 'image' => array(
'enabled' => false, 'enabled' => false,
'binary' => '/var/www/html/vendor/bin/wkhtmltoimage', 'binary' => '/usr/local/bin/wkhtmltoimage',
'timeout' => false, 'timeout' => false,
'options' => array(), 'options' => array(),
'env' => array(), 'env' => array(),

View File

@ -1,52 +0,0 @@
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">New Order Sent to Supplier</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">
<table class="table table-condensed" width="100%">
<tr>
<th>Account</th><td>{{ $o->account->company }}</td>
</tr>
<tr>
<th>Product</th><td>{{ $o->product->name }}: {{ $o->name }}</td>
</tr>
@if($o->date_last_invoice)
<tr>
<th>Last Invoice</th><td>{{ $o->date_last_invoice }}</td>
</tr>
<tr>
<th>Paid Until</th><td>{{ 'TBA' }}</td>
</tr>
<tr>
<th>Next Invoice</th><td>{{ $o->date_next_invoice }}</td>
</tr>
@endif
<tr>
<th>Ordered</th><td>{{ $o->date_orig->format('Y-m-d') }}</td>
</tr>
@if ($o->date_last)
<tr>
<th>Update</th><td>{{ $o->date_last->format('Y-m-d') }}</td>
</tr>
@endif
<tr>
<th>Order Details</th><td>{!! $o->order_info_details !!}</td>
</tr>
<tr>
<th>Reference:</th><td><input type="text" name="notes" class="" value="{{ \Illuminate\Support\Arr::get($o->order_info,'order_reference','') }}"></td>
</tr>
</table>
</div>
{{--
<div class="box-footer">
</div>
--}}
</div>

View File

@ -1,52 +0,0 @@
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">New Order Submitted</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">
<table class="table table-condensed" width="100%">
<tr>
<th>Account</th><td>{{ $o->account->company }}</td>
</tr>
<tr>
<th>Product</th><td>{{ $o->product->name }}: {{ $o->name }}</td>
</tr>
@if($o->date_last_invoice)
<tr>
<th>Last Invoice</th><td>{{ $o->date_last_invoice }}</td>
</tr>
<tr>
<th>Paid Until</th><td>{{ 'TBA' }}</td>
</tr>
<tr>
<th>Next Invoice</th><td>{{ $o->date_next_invoice }}</td>
</tr>
@endif
<tr>
<th>Ordered</th><td>{{ $o->date_orig->format('Y-m-d') }}</td>
</tr>
@if ($o->date_last)
<tr>
<th>Update</th><td>{{ $o->date_last->format('Y-m-d') }}</td>
</tr>
@endif
<tr>
<th>Order Details</th><td>{!! $o->order_info_details !!}</td>
</tr>
<tr>
<th>Save/Reject Note:</th><td><input type="text" name="notes" class=""></td>
</th>
</table>
</div>
{{--
<div class="box-footer">
</div>
--}}
</div>

View File

@ -0,0 +1,81 @@
@extends('adminlte::layouts.app')
@section('htmlheader_title')
{{ $o->sid }}
@endsection
@section('page_title')
{{ $o->sid }}
@endsection
@section('contentheader_title')
Service: {{ $o->sid }} <strong>{{ $o->product->name }}</strong>
@endsection
@section('contentheader_description')
{{ $o->sname }}: {{ $o->sdesc }}
@endsection
@section('main-content')
<div class="row">
<div class="col-6">
<div class="card">
<div class="card-header">
<div class="card-title">Update Order Status: {{ $o->order_status }}</div>
</div>
<form class="form-horizontal" method="post" action="{{ url('r/service/update',$o->id) }}">
{{ csrf_field() }}
<div class="card-body">
<div class="form-group row">
<label for="reference" class="col-sm-2 col-form-label text-right">Order Ref</label>
<div class="col-sm-6">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-hashtag"></i></span>
</div>
<input type="text" class="form-control" name="reference" value="{{ $o->order_info_reference ?? '' }}">
</div>
</div>
</div>
@includeIf('u.service.widgets.'.$o->stype.'.order',['o'=>$o->type])
<div class="form-group row">
<label for="notes" class="col-sm-2 col-form-label text-right">Notes</label>
<div class="col-sm-10">
<textarea class="textarea" name="notes">{{ $o->order_info_notes ?? '' }}</textarea>
</div>
</div>
</div>
<div class="card-footer">
<button type="reset" class="btn btn-secondary mr-2" onclick="window.history.go(-1); return false;">Cancel</button>
<button type="submit" class="btn btn-success">Submit</button>
</div>
</form>
</div>
</div>
</div>
@endsection
@section('page-scripts')
@css('//cdnjs.cloudflare.com/ajax/libs/summernote/0.8.12/summernote-bs4.css','summernote-css')
@js('//cdnjs.cloudflare.com/ajax/libs/summernote/0.8.12/summernote-bs4.js','summernote-js')
<script>
$(document).ready(function() {
$('.textarea').summernote({
minHeight: 350,
toolbar: [
['style', ['style']],
['font', ['bold', 'underline', 'clear']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['view', ['codeview', 'help']],
],
});
});
</script>
@append

View File

@ -26,7 +26,7 @@
<tr> <tr>
<td><a href="{{ url('u/service',$o->id) }}">{{ $o->id }}</a></td> <td><a href="{{ url('u/service',$o->id) }}">{{ $o->id }}</a></td>
<td>{{ $o->account->name }}</td> <td>{{ $o->account->name }}</td>
<td>{{ $o->name }}</td> <td>{{ $o->name_short }}</td>
<td>{{ $o->status }}</td> <td>{{ $o->status }}</td>
<td>{{ $o->product->name }}</td> <td>{{ $o->product->name }}</td>
</tr> </tr>

View File

@ -31,7 +31,9 @@
<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 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="#traffic" data-toggle="tab">Traffic</a></li>
--}} --}}
<li class="nav-item active"><a class="nav-link" href="#pending_items" data-toggle="tab">Pending Items</a></li> @if (! $o->suspend_billing AND ! $o->external_billing)
<li class="nav-item active"><a class="nav-link" href="#pending_items" data-toggle="tab">Pending Items</a></li>
@endif
{{-- {{--
<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="#invoices" data-toggle="tab">Invoices</a></li>
<li class="nav-item"><a class="nav-link" href="#emails" data-toggle="tab">Emails</a></li> <li class="nav-item"><a class="nav-link" href="#emails" data-toggle="tab">Emails</a></li>
@ -48,11 +50,10 @@
ACTION <span class="caret"></span> ACTION <span class="caret"></span>
</a> </a>
<div class="dropdown-menu dropdown-menu-right"> <div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" tabindex="-1" href="#tab_3">Action</a> @foreach($o->actions() as $action => $title)
<a class="dropdown-item" tabindex="-1" href="#">Another action</a> <a class="dropdown-item" tabindex="-1" href="{{ url('u/service/progress',['id'=>$o->id,'action'=>$action]) }}">{{ $title }}</a>
<a class="dropdown-item" tabindex="-1" href="#">Something else here</a> @endforeach
<div class="dropdown-divider"></div> {{-- <div class="dropdown-divider"></div> --}}
<a class="dropdown-item" tabindex="-1" href="#">Separated link</a>
</div> </div>
</li> </li>
</ul> </ul>
@ -67,9 +68,11 @@
<div class="tab-pane fade" id="product" role="tabpanel"> <div class="tab-pane fade" id="product" role="tabpanel">
Product. Product.
</div> </div>
<div class="tab-pane fade show active" id="pending_items" role="tabpanel"> @if (! $o->suspend_billing AND ! $o->external_billing)
@include('common.service.widget.invoice') <div class="tab-pane fade show active" id="pending_items" role="tabpanel">
</div> @include('common.service.widget.invoice')
</div>
@endif
<div class="tab-pane fade" id="invoices" role="tabpanel"> <div class="tab-pane fade" id="invoices" role="tabpanel">
Invoices. Invoices.
</div> </div>

View File

@ -0,0 +1,11 @@
<div class="form-group row">
<label for="reference" class="col-sm-2 col-form-label text-right">Service Number</label>
<div class="col-sm-6">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-fw fa-phone"></i></span>
</div>
<input type="text" class="form-control" name="broadband[service_number]" value="{{ $o->service_number ?? '' }}">
</div>
</div>
</div>

View File

@ -1,4 +1,12 @@
<div class="card"> <div class="card">
@if($o->external_billing)
<div class="ribbon-wrapper ribbon-lg">
<div class="ribbon bg-danger">
EXTERNAL BILLING
</div>
</div>
@endif
<div class="card-header bg-light"> <div class="card-header bg-light">
<h3 class="card-title">Service Information</h3> <h3 class="card-title">Service Information</h3>
</div> </div>
@ -13,7 +21,7 @@
<th>Status</th> <th>Status</th>
<td>{!! $o->status_html !!}</td> <td>{!! $o->status_html !!}</td>
</tr> </tr>
@if ($o->active or $o->isPending()) @if (($o->active OR $o->isPending()) AND ! $o->external_billing)
<tr> <tr>
<th>Billed</th> <th>Billed</th>
<td>{{ $o->billing_period }}</td> <td>{{ $o->billing_period }}</td>
@ -43,7 +51,7 @@
<td>@if ($o->autopay)Direct Debit @else Invoice @endif</td> <td>@if ($o->autopay)Direct Debit @else Invoice @endif</td>
</tr> </tr>
@elseif(! $o->wasCancelled()) @elseif($o->wasCancelled())
<tr> <tr>
<th>Cancelled</th> <th>Cancelled</th>
<td>{!! $o->date_end ? $o->date_end->format('Y-m-d') : $o->paid_to->format('Y-m-d').'<sup>*</sup>' !!}</td> <td>{!! $o->date_end ? $o->date_end->format('Y-m-d') : $o->paid_to->format('Y-m-d').'<sup>*</sup>' !!}</td>

View File

@ -43,6 +43,9 @@ Route::group(['middleware'=>['theme:adminlte-be','auth','role:reseller'],'prefix
Route::get('supplier/create','SuppliersController@create'); Route::get('supplier/create','SuppliersController@create');
Route::post('supplier/store','SuppliersController@store'); Route::post('supplier/store','SuppliersController@store');
Route::get('switch/start/{id}','\Leenooks\Controllers\AdminController@user_switch_start')->name('switch.user.stop'); Route::get('switch/start/{id}','\Leenooks\Controllers\AdminController@user_switch_start')->name('switch.user.stop');
Route::match(['get','post'],'service/update/{o}','ServiceController@update')
->where('o','[0-9]+')
->middleware('can:update,o');
}); });
// Our User Routes // Our User Routes
@ -63,6 +66,9 @@ Route::group(['middleware'=>['theme:adminlte-be','auth'],'prefix'=>'u'],function
Route::get('service/{o}','UserHomeController@service') Route::get('service/{o}','UserHomeController@service')
->where('o','[0-9]+') ->where('o','[0-9]+')
->middleware('can:view,o'); ->middleware('can:view,o');
Route::get('service/progress/{o}/{status}','UserHomeController@service_progress')
->where('o','[0-9]+')
->middleware('can:progress,o,status');
}); });
// Frontend Routes (Non-Authed Users) // Frontend Routes (Non-Authed Users)