More work on ordering

This commit is contained in:
Deon George 2018-08-11 15:09:41 +10:00
parent 499d44289e
commit 5373e6b246
No known key found for this signature in database
GPG Key ID: 7670E8DC27415254
33 changed files with 730 additions and 163 deletions

View File

@ -3,9 +3,13 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\Product;
use Igaster\LaravelTheme\Facades\Theme; use Igaster\LaravelTheme\Facades\Theme;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Database\Eloquent\Model;
use App\Models\{Product,Service};
use App\User;
class OrderController extends Controller class OrderController extends Controller
{ {
@ -30,5 +34,50 @@ class OrderController extends Controller
public function submit(Request $request) public function submit(Request $request)
{ {
Validator::make($request->all(),[
'product_id'=>'required|exists:ab_product,id',
])
->sometimes('order_email','required|email',function($input) use ($request) {
return ($input->order_email AND ! $input->order_email_manual) OR (! $input->order_email_manual);
})
->sometimes('order_email_manual','required|email',function($input) use ($request) {
return $input->order_email_manual AND ! $input->order_email;
})->validate();
// Check the plugin details.
$po = Product::findOrFail($request->post('product_id'));
// Check we have the custom attributes for the product
$options = $po->orderValidation($request);
$uo = User::where('email','=',$request->post('order_email') ?: $request->post('order_email_manual'))->firstOrFail();
$ao = $request->input('account_id')
? $uo->accounts->where('account_id',$request->input('account_id'))
: $uo->accounts->first();
$so = new Service;
$so->id = Service::NextId();
// @todo Make this automatic
$so->site_id = config('SITE_SETUP')->id;
$so->product_id = $request->post('product_id');
$so->order_status = 'ORDER-SUBMIT';
$so->orderby_id = Auth::user()->id;
if ($options->order_info)
{
$so->order_info = $options->order_info;
unset($options->order_info);
}
$so = $ao->services()->save($so);
if ($options instanceOf Model) {
$options->service_id = $so->id;
$options->save();
}
return view('order_received',['o'=>$so]);
} }
} }

View File

@ -20,4 +20,9 @@ class ResellerServicesController extends Controller
{ {
return ['data'=>Auth::user()->all_clients()->values()]; return ['data'=>Auth::user()->all_clients()->values()];
} }
public function service_movements()
{
return ['data'=>Auth::user()->all_client_service_movements()->values()];
}
} }

30
app/Models/AdslPlan.php Normal file
View File

@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Traits\OrderServiceOptions;
class AdslPlan extends Model
{
use OrderServiceOptions;
protected $table = 'ab_adsl_plan';
protected $order_attributes = [
'options.address'=>[
'request'=>'options.address',
'key'=>'service_address',
'validation'=>'required|string:10',
'validation_message'=>'Address is a required field.',
],
];
protected $order_model = ServiceAdsl::class;
public function product()
{
return $this->hasOne(AdslSupplierPlan::class,'id','adsl_supplier_plan_id');
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AdslSupplierPlan extends Model
{
protected $table = 'ab_adsl_supplier_plan';
public function getNameAttribute()
{
return $this->speed;
}
}

35
app/Models/PlanVoip.php Normal file
View File

@ -0,0 +1,35 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Traits\OrderServiceOptions;
class PlanVoip extends Model
{
use OrderServiceOptions;
protected $order_attributes = [
'options.phonenumber'=>[
'request'=>'options.phonenumber',
'key'=>'service_number',
'validation'=>'required|min:10',
'validation_message'=>'Phone Number is a required field.',
],
'options.supplier'=>[
'request'=>'options.supplier',
'key'=>'order_info.supplier',
'validation'=>'required|min:4',
'validation_message'=>'Phone Supplier is a required field.',
],
'options.supplieraccnum'=>[
'request'=>'options.supplieraccnum',
'key'=>'order_info.supplieraccnum',
'validation'=>'required|min:4',
'validation_message'=>'Phone Supplier Account Number is a required field.',
],
];
protected $order_model = ServiceVoip::class;
}

View File

@ -4,6 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
class Product extends Model class Product extends Model
{ {
@ -25,22 +26,93 @@ class Product extends Model
return $this->prod_plugin_file; return $this->prod_plugin_file;
} }
public function getContractTermAttribute()
{
switch ($this->prod_plugin_file) {
case 'ADSL': return $this->plugin()->contract_term;
// @todo Incorporate into DB
case 'VOIP': return 12;
// @todo Change this after contracts implemented.
default:
return 'TBA';
}
}
public function getDefaultBillingAttribute()
{
return array_get($this->PricePeriods(),$this->price_recurr_default);
}
public function getDefaultCostAttribute()
{
// @todo Integrate this into a Tax::class
return array_get($this->price_array,sprintf('%s.1.price_base',$this->price_recurr_default))*1.1;
}
private function getDefaultLanguage()
{
return config('SITE_SETUP')->language;
}
public function getDescriptionAttribute() public function getDescriptionAttribute()
{ {
// @todo If the user has selected a specific language. // @todo If the user has selected a specific language.
return $this->description($this->getDefaultLanguage()); return $this->description($this->getDefaultLanguage());
} }
public function getMinimumCostAttribute()
{
$table = [
0=>4,
1=>1,
2=>1/3,
3=>1/6,
4=>1/12,
5=>1/24,
6=>1/36,
7=>1/48,
8=>1/60,
];
return $this->setup_cost + ( $this->default_cost * array_get($table,$this->price_recurr_default) * $this->contract_term);
}
public function getNameAttribute() public function getNameAttribute()
{ {
return $this->name(Auth::user()->language); return $this->name(Auth::user()->language);
} }
public function getProductTypeAttribute()
{
return $this->plugin()->product->name;
}
public function getPriceArrayAttribute()
{
return unserialize($this->price_group);
}
public function getPriceTypeAttribute()
{
$table = [
0=>_('One-time Charge'),
1=>_('Recurring Membership/Subscription'),
2=>_('Trial for Membership/Subscription'),
];
}
public function getProductIdAttribute() public function getProductIdAttribute()
{ {
return sprintf('#%04s',$this->id); return sprintf('#%04s',$this->id);
} }
public function getSetupCostAttribute()
{
// @todo Integrate this into a Tax::class
return array_get($this->price_array,sprintf('%s.1.price_setup',$this->price_recurr_default))*1.1;
}
public function scopeActive() public function scopeActive()
{ {
return $this->where('active',TRUE); return $this->where('active',TRUE);
@ -54,9 +126,34 @@ class Product extends Model
return $this->descriptions->where('language_id',$lo->id)->first()->description_short; return $this->descriptions->where('language_id',$lo->id)->first()->description_short;
} }
private function getDefaultLanguage() public function orderValidation(Request $request)
{ {
return config('SITE_SETUP')->language; return $this->plugin()->orderValidation($request);
}
private function plugin()
{
switch ($this->prod_plugin_file) {
case 'ADSL':
return AdslPlan::findOrFail($this->prod_plugin_data);
case 'VOIP':
return new PlanVoip;
}
}
public function PricePeriods()
{
return [
0=>_('Weekly'),
1=>_('Monthly'),
2=>_('Quarterly'),
3=>_('Semi-Annually'),
4=>_('Annually'),
5=>_('Two years'),
6=>_('Three Years'),
7=>_('Four Years'),
8=>_('Five Years'),
];
} }
/** /**

View File

@ -3,14 +3,25 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use App\Traits\NextKey;
class Service extends Model class Service extends Model
{ {
use NextKey;
protected $table = 'ab_service'; protected $table = 'ab_service';
protected $with = ['product.descriptions','account.language','service_adsl','service_domain.tld','service_ssl','service_voip']; protected $with = ['product.descriptions','account.language','service_adsl','service_domain.tld','service_ssl','service_voip'];
protected $dates = ['date_last_invoice','date_next_invoice']; protected $dates = ['date_last_invoice','date_next_invoice'];
protected $casts = [
'order_info'=>'array',
];
public $incrementing = FALSE;
const CREATED_AT = 'date_orig';
const UPDATED_AT = 'date_last';
protected $appends = [ protected $appends = [
'account_name',
'category', 'category',
'name', 'name',
'next_invoice', 'next_invoice',
@ -19,7 +30,9 @@ class Service extends Model
'service_id_url', 'service_id_url',
'status', 'status',
]; ];
protected $visible = [ protected $visible = [
'account_name',
'active', 'active',
'category', 'category',
'data_orig', 'data_orig',
@ -32,6 +45,10 @@ class Service extends Model
'status', 'status',
]; ];
private $inactive_status = [
'CANCELLED',
];
public function account() public function account()
{ {
return $this->belongsTo(Account::class); return $this->belongsTo(Account::class);
@ -70,9 +87,16 @@ class Service extends Model
/** /**
* Only query active categories * Only query active categories
*/ */
public function scopeActive() public function scopeActive($query)
{ {
return $this->where('active',TRUE); return $query->where(function () use ($query) {
return $query->where('active',TRUE)->orWhereNotIn('order_status',$this->inactive_status);
});
}
public function getAccountNameAttribute()
{
return $this->account->company;
} }
public function getCategoryAttribute() public function getCategoryAttribute()
@ -82,10 +106,10 @@ class Service extends Model
public function getNameAttribute() public function getNameAttribute()
{ {
if (! isset($this->getServiceDetail()->name)) if (! isset($this->ServicePlugin()->name))
return 'Unknown'; return 'Unknown';
return $this->getServiceDetail()->name; return $this->ServicePlugin()->name;
} }
public function getNextInvoiceAttribute() public function getNextInvoiceAttribute()
@ -98,28 +122,6 @@ class Service extends Model
return $this->product->name($this->account->language); return $this->product->name($this->account->language);
} }
/**
* This function will return the associated service model for the product type
*/
public function getServiceDetail()
{
switch ($this->product->prod_plugin_file)
{
case 'ADSL': return $this->service_adsl;
case 'DOMAIN': return $this->service_domain;
case 'HOST': return $this->service_host;
case 'SSL': return $this->service_ssl;
case 'VOIP': return $this->service_voip;
default: return NULL;
}
}
public function getStatusAttribute()
{
return $this->active ? 'Active' : 'Inactive';
}
public function getServiceExpireAttribute() public function getServiceExpireAttribute()
{ {
return 'TBA'; return 'TBA';
@ -139,4 +141,41 @@ class Service extends Model
{ {
return sprintf('%02s.%04s.%04s',$this->site_id,$this->account_id,$this->id); return sprintf('%02s.%04s.%04s',$this->site_id,$this->account_id,$this->id);
} }
public function getStatusAttribute()
{
return $this->order_status ? $this->order_status : ( $this->active ? 'Active' : 'Inactive');
}
public function setDateOrigAttribute($value)
{
$this->attributes['date_orig'] = $value->timestamp;
}
public function setDateLastAttribute($value)
{
$this->attributes['date_last'] = $value->timestamp;
}
public function isActive()
{
return $this->active OR ($this->order_status AND ! in_array($this->order_status,$this->inactive_status));
}
/**
* This function will return the associated service model for the product type
*/
private function ServicePlugin()
{
switch ($this->product->prod_plugin_file)
{
case 'ADSL': return $this->service_adsl;
case 'DOMAIN': return $this->service_domain;
case 'HOST': return $this->service_host;
case 'SSL': return $this->service_ssl;
case 'VOIP': return $this->service_voip;
default: return NULL;
}
}
} }

View File

@ -3,13 +3,17 @@
namespace App\Models; namespace App\Models;
use App\Models\Service_Model as Model; use App\Models\Service_Model as Model;
use App\Traits\NextKey;
class ServiceAdsl extends Model class ServiceAdsl extends Model
{ {
use NextKey;
protected $table = 'ab_service__adsl'; protected $table = 'ab_service__adsl';
public $timestamps = FALSE;
public function getNameAttribute() public function getNameAttribute()
{ {
return $this->service_number; return $this->service_number ?: $this->service_address;
} }
} }

View File

@ -3,10 +3,14 @@
namespace App\Models; namespace App\Models;
use App\Models\Service_Model as Model; use App\Models\Service_Model as Model;
use App\Traits\NextKey;
class ServiceDomain extends Model class ServiceDomain extends Model
{ {
use NextKey;
protected $table = 'ab_service__domain'; protected $table = 'ab_service__domain';
public $timestamps = FALSE;
public function tld() public function tld()
{ {

View File

@ -3,10 +3,14 @@
namespace App\Models; namespace App\Models;
use App\Models\Service_Model as Model; use App\Models\Service_Model as Model;
use App\Traits\NextKey;
class ServiceHost extends Model class ServiceHost extends Model
{ {
use NextKey;
protected $table = 'ab_service__hosting'; protected $table = 'ab_service__hosting';
public $timestamps = FALSE;
public function getNameAttribute() public function getNameAttribute()
{ {

View File

@ -3,10 +3,13 @@
namespace App\Models; namespace App\Models;
use App\Models\Service_Model as Model; use App\Models\Service_Model as Model;
use App\Traits\NextKey;
use App\Classes\SSL; use App\Classes\SSL;
class ServiceSsl extends Model class ServiceSsl extends Model
{ {
use NextKey;
protected $table = 'ab_service__ssl'; protected $table = 'ab_service__ssl';
protected $_o = NULL; protected $_o = NULL;

View File

@ -3,10 +3,13 @@
namespace App\Models; namespace App\Models;
use App\Models\Service_Model as Model; use App\Models\Service_Model as Model;
use App\Traits\NextKey;
class ServiceVoip extends Model class ServiceVoip extends Model
{ {
use NextKey;
protected $table = 'ab_service__voip'; protected $table = 'ab_service__voip';
public $timestamps = FALSE;
public function getNameAttribute() public function getNameAttribute()
{ {

24
app/Traits/NextKey.php Normal file
View File

@ -0,0 +1,24 @@
<?php
/**
* Works out the next ID to use for an Eloquent Table.
*/
namespace App\Traits;
trait NextKey
{
public static function boot()
{
parent::boot();
static::creating(function($model)
{
$model->id = self::NextId();
});
}
public static function NextId()
{
return (new self)->max('id')+1;
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* Creates and returns the Service Options Model for an Order.
*/
namespace App\Traits;
use Illuminate\Http\Request;
trait OrderServiceOptions
{
/*
protected $order_attributes = [
'options.input'=>[
'request'=>'options.input',
'key'=>'column',
'validation'=>'required|string:10',
'validation_message'=>'It is a required field.',
],
];
protected $order_model = NULL;
*/
public function orderValidation(Request $request)
{
if (! isset($this->order_attributes))
return NULL;
$request->validate(collect($this->order_attributes)->pluck('validation','request')->toArray());
if (! isset($this->order_model))
return NULL;
$o = new $this->order_model;
$x = [];
foreach ($this->order_attributes as $k => $v)
{
$x[$v['key']] = $request->input($k);
}
$o->forceFill(array_undot($x));
// @todo Make this automatic
$o->site_id = config('SITE_SETUP')->id;
return $o;
}
}

View File

@ -9,6 +9,7 @@ use Laravel\Passport\HasApiTokens;
use Leenooks\Carbon; use Leenooks\Carbon;
use Leenooks\Traits\UserSwitch; use Leenooks\Traits\UserSwitch;
use App\Notifications\ResetPasswordNotification; use App\Notifications\ResetPasswordNotification;
use App\Models\Service;
class User extends Authenticatable class User extends Authenticatable
{ {
@ -159,8 +160,9 @@ class User extends Authenticatable
public function getServicesActiveAttribute() public function getServicesActiveAttribute()
{ {
return $this->services return $this->services->filter(function($item) {
->where('active',TRUE); return $item->isActive();
});
} }
public function getServicesCountHtmlAttribute() public function getServicesCountHtmlAttribute()
@ -188,6 +190,11 @@ class User extends Authenticatable
return sprintf('<a href="/u/account/view/%s">%s</a>',$this->id,$this->user_id); return sprintf('<a href="/u/account/view/%s">%s</a>',$this->id,$this->user_id);
} }
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPasswordNotification($token));
}
/** Scopes **/ /** Scopes **/
public function scopeActive() public function scopeActive()
@ -195,6 +202,17 @@ class User extends Authenticatable
return $this->where('active',TRUE); return $this->where('active',TRUE);
} }
/**
* Determine if the user is an admin of the account with $id
*
* @param $id
* @return bool
*/
public function isAdmin($id)
{
return $id AND $this->isReseller() AND in_array($id,$this->all_accounts()->pluck('id')->toArray());
}
/** Functions */ /** Functions */
public function all_accounts() public function all_accounts()
@ -208,7 +226,6 @@ class User extends Authenticatable
return $result->flatten(); return $result->flatten();
} }
public function all_clients($level=0) public function all_clients($level=0)
{ {
$result = collect(); $result = collect();
@ -229,6 +246,16 @@ class User extends Authenticatable
return $result->flatten(); return $result->flatten();
} }
public function all_client_service_movements()
{
$s = Service::active()->where('order_status','!=','ACTIVE');
$aa = $this->all_accounts()->pluck('id')->unique()->toArray();
return $s->get()->filter(function($item) use ($aa) {
return in_array($item->account_id,$aa);
});
}
// List all the agents, including agents of agents // List all the agents, including agents of agents
public function all_agents($level=0) public function all_agents($level=0)
{ {
@ -250,17 +277,6 @@ class User extends Authenticatable
return $result->flatten(); return $result->flatten();
} }
/**
* Determine if the user is an admin of the account with $id
*
* @param $id
* @return bool
*/
public function isAdmin($id)
{
return $id AND $this->isReseller() AND in_array($id,$this->all_accounts()->pluck('id')->toArray());
}
/** /**
* Determine if the logged in user is a reseller or wholesaler * Determine if the logged in user is a reseller or wholesaler
* *
@ -285,8 +301,4 @@ class User extends Authenticatable
elseif (! $this->all_agents()->count() AND ! $this->all_clients()->count()) elseif (! $this->all_agents()->count() AND ! $this->all_clients()->count())
return 'customer'; return 'customer';
} }
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPasswordNotification($token));
}
} }

6
composer.lock generated
View File

@ -2463,11 +2463,11 @@
}, },
{ {
"name": "leenooks/laravel", "name": "leenooks/laravel",
"version": "0.2.0", "version": "0.2.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://dev.leenooks.net/leenooks/laravel", "url": "https://dev.leenooks.net/leenooks/laravel",
"reference": "98b7b9f6a80274f40c7c02f3281ba78ecfb27603" "reference": "f8d743296580ccf17260505be0b5baee17ac82c6"
}, },
"require": { "require": {
"igaster/laravel-theme": "2.0.6", "igaster/laravel-theme": "2.0.6",
@ -2503,7 +2503,7 @@
"laravel", "laravel",
"leenooks" "leenooks"
], ],
"time": "2018-08-07T14:14:48+00:00" "time": "2018-08-11T05:11:34+00:00"
}, },
{ {
"name": "maximebf/debugbar", "name": "maximebf/debugbar",

View File

@ -0,0 +1,38 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class ServiceAddStatus extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('ab_service', function (Blueprint $table) {
$table->integer('orderby_id')->unsigned()->nullable();
$table->string('order_status')->nullable();
$table->json('order_info')->nullable();
$table->foreign('orderby_id')->references('id')->on('users');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('ab_service', function (Blueprint $table) {
$table->dropForeign(['orderby_id']);
$table->dropColumn('orderby_id');
$table->dropColumn('order_status');
$table->dropColumn('order_info');
});
}
}

View File

@ -12,13 +12,47 @@
@endsection @endsection
@section('main-content') @section('main-content')
<div class="row">
@include('widgets.account_summary')
</div>
<ul class="nav nav-tabs">
<li class="active">
<a href="#personal-tab" data-toggle="tab">Personal</a>
</li>
<li>
<a href="#clients-tab" data-toggle="tab">Clients</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="personal-tab">
<div class="row">
<div class="col-xs-7">
@include('widgets.services_active')
</div>
<div class="col-xs-5">
@include('widgets.invoices_due')
</div>
<div class="col-xs-5">
@include('widgets.payment_history')
</div>
</div>
</div>
<div class="tab-pane" id="clients-tab">
<div class="row">
<div class="col-xs-6"> <div class="col-xs-6">
@include('r.accounts') @include('r.accounts')
</div> </div>
<div class="col-xs-6">
@include('r.service_movements')
</div>
<div class="col-xs-6"> <div class="col-xs-6">
@include('r.agents') @include('r.agents')
</div> </div>
<div class="col-xs-6"> <div class="col-xs-6">
@include('r.clients') @include('r.clients')
</div> </div>
</div>
</div>
</div>
@endsection @endsection

View File

@ -0,0 +1,81 @@
<div class="box box-warning small">
<div class="box-header">
<h3 class="box-title">Service Movements</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_movements()->count())
<table class="table table-bordered table-striped table-hover" id="service_movements" 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_movements()->count() }}</th>
<th colspan="4">&nbsp;</th>
</tr>
</tfoot>
</table>
@else
<p>No Service Movements</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_movements').DataTable( {
responsive: true,
ajax: {
url: "/api/r/service_movements"
},
columns: [
{ data: "service_id_url" },
{ data: "account_name" },
{ data: "name" },
{ data: "status" },
{ data: "product_name" }
],
language: {
emptyTable: "No Active Clients"
},
order: [1, 'asc'],
rowGroup: {
dataSrc: 'product_name',
startRender: null,
endRender: function ( rows, group ) {
return rows.count()+' x ' + group;
},
},
orderFixed: [4, 'asc']
});
$('#service_movements tbody').on('click','tr', function () {
$(this).toggleClass('selected');
});
});
</script>
@append

View File

@ -14,44 +14,7 @@
@section('main-content') @section('main-content')
<div class="content"> <div class="content">
<div class="row"> <div class="row">
@if ($o->accounts->count() > 2) @include('widgets.account_summary')
<div class="col-sm-3">
<div class="info-box">
<span class="info-box-icon bg-orange"><i class="fa fa-user"></i></span>
<div class="info-box-content">
<span class="info-box-text">Accounts Linked</span>
<span class="info-box-number">{{ $o->accounts->count() }}</span>
</div>
</div>
</div>
@endif
<div class="col-sm-3">
<div class="info-box">
<span class="info-box-icon bg-red"><i class="fa fa-dollar"></i></span>
<div class="info-box-content">
<span class="info-box-text">Account Balance</span>
<span class="info-box-number"><small>$</small> {{ number_format($o->invoices_due->sum('due'),2) }}</span>
</div>
</div>
</div>
<div class="col-sm-3">
<div class="info-box">
<span class="info-box-icon bg-green"><i class="fa fa-clone"></i></span>
<div class="info-box-content">
<span class="info-box-text">Active Services</span>
<span class="info-box-number">{{ $o->services_active->count() }} <small>/{{ $o->services->count() }}</small></span>
</div>
</div>
</div>
<div class="col-sm-3">
<div class="info-box">
<span class="info-box-icon bg-blue"><i class="fa fa-hashtag"></i></span>
<div class="info-box-content">
<span class="info-box-text">Invoices Due</span>
<span class="info-box-number">{{ $o->invoices_due->count() }}</span>
</div>
</div>
</div>
</div> </div>
<div class="row"> <div class="row">

View File

@ -0,0 +1,38 @@
@if ($o->accounts->count() > 2)
<div class="col-sm-3">
<div class="info-box">
<span class="info-box-icon bg-orange"><i class="fa fa-user"></i></span>
<div class="info-box-content">
<span class="info-box-text">Accounts Linked</span>
<span class="info-box-number">{{ $o->accounts->count() }}</span>
</div>
</div>
</div>
@endif
<div class="col-sm-3">
<div class="info-box">
<span class="info-box-icon bg-red"><i class="fa fa-dollar"></i></span>
<div class="info-box-content">
<span class="info-box-text">Account Balance</span>
<span class="info-box-number"><small>$</small> {{ number_format($o->invoices_due->sum('due'),2) }}</span>
</div>
</div>
</div>
<div class="col-sm-3">
<div class="info-box">
<span class="info-box-icon bg-green"><i class="fa fa-clone"></i></span>
<div class="info-box-content">
<span class="info-box-text">Active Services</span>
<span class="info-box-number">{{ $o->services_active->count() }} <small>/{{ $o->services->count() }}</small></span>
</div>
</div>
</div>
<div class="col-sm-3">
<div class="info-box">
<span class="info-box-icon bg-blue"><i class="fa fa-hashtag"></i></span>
<div class="info-box-content">
<span class="info-box-text">Invoices Due</span>
<span class="info-box-number">{{ $o->invoices_due->count() }}</span>
</div>
</div>
</div>

View File

@ -49,14 +49,6 @@
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
$('#services').DataTable( { $('#services').DataTable( {
rowGroup: {
dataSrc: 'product_name',
startRender: null,
endRender: function ( rows, group ) {
return rows.count()+' x ' + group;
},
},
orderFixed: [3, 'asc'],
responsive: true, responsive: true,
ajax: { ajax: {
url: "/api/u/services" url: "/api/u/services"
@ -72,7 +64,15 @@
language: { language: {
emptyTable: "No Active Services" emptyTable: "No Active Services"
}, },
order: [5, 'asc'] order: [5, 'asc'],
rowGroup: {
dataSrc: 'product_name',
startRender: null,
endRender: function ( rows, group ) {
return rows.count()+' x ' + group;
},
},
orderFixed: [3, 'asc']
}); });
$('#services tbody').on('click','tr', function () { $('#services tbody').on('click','tr', function () {

View File

@ -65,30 +65,30 @@
<div class="col-md-6"> <div class="col-md-6">
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="active"> <li>
<a href="#tab_1" data-toggle="tab">New Client</a> <a href="#tab_1" data-toggle="tab">New Client</a>
</li> </li>
<li> <li class="active">
<a href="#tab_2" data-toggle="tab">Existing Client</a> <a href="#tab_2" data-toggle="tab">Existing Client</a>
</li> </li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane row fade in active" id="tab_1"> <div class="tab-pane row fade" id="tab_1">
<div class="control-group form-group col-sm-12 {{ $errors->has('order_email') ? 'has-error' : '' }}"> <div class="control-group form-group col-sm-12 {{ $errors->has('order_email_manual') ? 'has-error' : '' }}">
<label for="order_email">New Client Email</label> <label for="order_email_manual">New Client Email</label>
<input type="email" class="form-control" id="order_email" name="order_email" placeholder="New Client Email" value="{{ old('order_email') }}"> <input type="email" class="form-control" id="order_email_manual" name="order_email_manual" placeholder="New Client Email" value="{{ old('order_email_manual') }}">
<span class="help-block">{{ $errors->first('order_email') }}</span> <span class="help-block">{{ $errors->first('order_email_manual') }}</span>
</div> </div>
</div> </div>
<div class="tab-pane row fade" id="tab_2"> <div class="tab-pane row fade in active" id="tab_2">
<div class="control-group form-group col-sm-12 {{ $errors->has('order_email') ? 'has-error' : '' }}"> <div class="control-group form-group col-sm-12 {{ $errors->has('order_email') ? 'has-error' : '' }}">
<label for="order_email">Client Account</label> <label for="order_email">Client Account</label>
<select class="form-control" id="order_email" name="order_email" value="{{ old('order_email') }}"> <select class="form-control" id="order_email" name="order_email">
<option value="">&nbsp;</option> <option value="">&nbsp;</option>
@foreach ($user->all_clients()->sortBy('sur_first_name') as $o) @foreach ($user->all_clients()->sortBy('sur_first_name') as $o)
<option value="{{ $o->email }}">{{ $o->sur_first_name }}</option> <option value="{{ $o->email }}" @if($o->email == old('order_email')) selected @endif>{{ $o->sur_first_name }}</option>
@endforeach @endforeach
</select> </select>
<span class="help-block">{{ $errors->first('order_email') }}</span> <span class="help-block">{{ $errors->first('order_email') }}</span>
@ -124,6 +124,8 @@
</div> </div>
</div> </div>
</div> </div>
@else
<input type="hidden" name="order_email" value="{{ $user->email }}">
@endif @endif
<!-- Product --> <!-- Product -->
@ -135,27 +137,48 @@
<div class="panel-collapse margin-bottom-20"> <div class="panel-collapse margin-bottom-20">
<div class="panel-body"> <div class="panel-body">
<div class="col-md-12"> <div class="col-md-12">
<div class="control-group form-group col-sm-12 {{ $errors->has('product_options') ? 'has-error' : '' }}">
<span class="help-block">{{ $errors->first('product_options') }}</span>
</div>
<div class="control-group form-group col-sm-6 {{ $errors->has('product_id') ? 'has-error' : '' }}"> <div class="control-group form-group col-sm-6 {{ $errors->has('product_id') ? 'has-error' : '' }}">
<label for="order_email">Product</label> <label for="order_email">Product</label>
<select class="form-control" id="product_id" name="product_id" value="{{ old('product_id') }}"> <select class="form-control" id="product_id" name="product_id">
<option value="">&nbsp;</option> <option value="">&nbsp;</option>
@php
$po = $selected = NULL;
@endphp
@foreach (\App\Models\Product::active()->get()->sortBy('name') as $o) @foreach (\App\Models\Product::active()->get()->sortBy('name') as $o)
<option value="{{ $o->id }}">{{ $o->name }}</option> @php
if ($o->id == old('product_id'))
{
$selected = 'selected';
$po = $o;
} else {
$selected = NULL;
}
@endphp
<option value="{{ $o->id }}" {{ $selected }}>{{ $o->name }}</option>
@endforeach @endforeach
</select> </select>
<span class="help-block">{{ $errors->first('product_id') }}</span> <span class="help-block">{{ $errors->first('product_id') }}</span>
</div> </div>
<div class="col-sm-6" id="product_info"></div> <div class="col-sm-6" id="product_info">
@if (old('product_id'))
@include('widgets.product_description',['o'=>$po])
@endif
</div>
</div> </div>
<div class="col-md-12" id="product_order"></div> <div class="col-md-12" id="product_order">
@if (old('product_id'))
@include('widgets.product_order',['o'=>$po])
@endif
</div>
<div class="row"> <div class="row">
<div class="col-sm-2"><button class="btn btn-block btn-primary">Previous</button></div> <div class="col-sm-2"><button class="btn btn-block btn-primary">Previous</button></div>
</div> <div class="col-sm-2 pull-right"><input type="submit" class="btn btn-block btn-primary" value="Submit Order"></div>
<div class="row">
<div class="col-sm-2"><input type="submit" class="btn btn-block btn-primary" value="Submit Order"></div>
</div> </div>
</div> </div>
</div> </div>
@ -205,7 +228,6 @@
// Send the request and update sub category dropdown // Send the request and update sub category dropdown
$.ajax({ $.ajax({
type: "GET", type: "GET",
//data: "key="+$(this).val(),
dataType: "html", dataType: "html",
cache: true, cache: true,
url: '{{ url('product_info') }}'+'/'+$(this).val(), url: '{{ url('product_info') }}'+'/'+$(this).val(),
@ -220,7 +242,7 @@
$.ajax({ $.ajax({
type: "GET", type: "GET",
//data: "key="+$(this).val(), // data: "old=",
dataType: "html", dataType: "html",
cache: true, cache: true,
url: '{{ url('product_order') }}'+'/'+$(this).val(), url: '{{ url('product_order') }}'+'/'+$(this).val(),

View File

@ -0,0 +1,12 @@
@extends('layouts.app')
@section('htmlheader_title')
{{ trans('message.home') }}
@endsection
@section('main-content')
<div class="col-md-12">
<h2>Order Service</h2>
<p>Your order has been received - #{{ $o->id }}. An email will be sent to you with as your order progresses.</p>
</div>
@endsection

View File

@ -1,9 +1,9 @@
<fieldset class="form-group col-sm-12"> <fieldset class="form-group col-sm-12">
<label>ADSL</label> <label>ADSL</label>
<div class="form-group col-sm-12 {{ $errors->has('product_options.address') ? 'has-error' : '' }}"> <div class="form-group col-sm-12 {{ $errors->has('options.address') ? 'has-error' : '' }}">
<label for="product_options.address">Site Address</label> <label for="options.address">Site Address</label>
<input type="text" class="form-control" id="product_options.address" name="product_options[address]" placeholder="Site Address" value="{{ old('product_options.address') }}"> <input type="text" class="form-control" id="options.address" name="options[address]" placeholder="Site Address" value="{{ old('options.address') }}">
<span class="help-block">{{ $errors->first('product_options.address') }} {{ $errors->first('product_options.address') }}</span> <span class="help-block">{{ $errors->first('options.address') }}</span>
</div> </div>
</fieldset> </fieldset>

View File

@ -1,21 +1,21 @@
<fieldset class="form-group"> <fieldset class="form-group">
<label class="col-md-12">VOIP</label> <label class="col-md-12">VOIP</label>
<div class="form-group col-sm-6 {{ $errors->has('product_options.phonenumber') ? 'has-error' : '' }}"> <div class="form-group col-sm-6 {{ $errors->has('options.phonenumber') ? 'has-error' : '' }}">
<label for="product_options.phonenumber">Phone Number</label> <label for="options.phonenumber">Phone Number</label>
<input type="text" class="form-control" id="product_options.phonenumber" name="product_options[phonenumber]" placeholder="Phone Number with Area Code" value="{{ old('product_options.phonenumber') }}"> <input type="text" class="form-control" id="options.phonenumber" name="options[phonenumber]" placeholder="Phone Number with Area Code" value="{{ old('options.phonenumber') }}">
<span class="help-block">{{ $errors->first('product_options.phonenumber') }} {{ $errors->first('product_options.phonenumber') }}</span> <span class="help-block">{{ $errors->first('options.phonenumber') }}</span>
</div> </div>
<div class="form-group col-sm-6 {{ $errors->has('product_options.supplier') ? 'has-error' : '' }}"> <div class="form-group col-sm-6 {{ $errors->has('options.supplier') ? 'has-error' : '' }}">
<label for="product_order.supplier">Existing Supplier</label> <label for="options.supplier">Existing Supplier</label>
<input type="text" class="form-control" id="product_options.supplier" name="product_options[supplier]" placeholder="eg: Telstra" value="{{ old('product_options.supplier') }}"> <input type="text" class="form-control" id="options.supplier" name="options[supplier]" placeholder="eg: Telstra" value="{{ old('options.supplier') }}">
<span class="help-block">{{ $errors->first('product_options.supplier') }} {{ $errors->first('product_options.supplier') }}</span> <span class="help-block">{{ $errors->first('options.supplier') }}</span>
</div> </div>
<div class="form-group col-sm-6 {{ $errors->has('product_options.supplieraccnum') ? 'has-error' : '' }}"> <div class="form-group col-sm-6 {{ $errors->has('options.supplieraccnum') ? 'has-error' : '' }}">
<label for="product_options.supplieraccnum">Suppliers Account Number</label> <label for="options.supplieraccnum">Suppliers Account Number</label>
<input type="text" class="form-control" id="product_options.supplieraccnum" name="product_options[supplieraccnum]" placeholder="Refer to Bill" value="{{ old('product_options.supplieraccnum') }}"> <input type="text" class="form-control" id="options.supplieraccnum" name="options[supplieraccnum]" placeholder="Refer to Bill" value="{{ old('options.supplieraccnum') }}">
<span class="help-block">{{ $errors->first('product_options.supplieraccnum') }} {{ $errors->first('product_options.supplieraccnum') }}</span> <span class="help-block">{{ $errors->first('options.supplieraccnum') }}</span>
</div> </div>
</fieldset> </fieldset>

View File

@ -5,26 +5,30 @@
<table class="table table-condensed"> <table class="table table-condensed">
<tr> <tr>
<th>Type</th> <th>Type</th>
<td class="text-right">ADSL</td> <td class="text-right">{{ $o->product_type }}</td>
</tr> </tr>
<tr> <tr>
<th>Setup Cost</th> <th>Setup Charges <sup>*</sup></th>
<td class="text-right">TBA</td> <td class="text-right">${{ is_numeric($o->setup_cost) ? number_format($o->setup_cost,2) : $o->setup_cost }}</td>
</tr> </tr>
<tr> <tr>
<th>Monthly Cost</th> <th>Cost</th>
<td class="text-right">TBA</td> <td class="text-right">${{ is_numeric($o->default_cost) ? number_format($o->default_cost,2) : $o->default_cost }}</td>
</tr> </tr>
<tr> <tr>
<th>Default Billing</th> <th>Default Billing</th>
<td class="text-right">TBA</td> <td class="text-right">{{ is_numeric($o->default_billing) ? number_format($o->default_billing,2) : $o->default_billing }}</td>
</tr> </tr>
<tr> <tr>
<th>Contract Term</th> <th>Contract Term</th>
<td class="text-right">TBA</td> <td class="text-right">{{ $o->contract_term }} mths</td>
</tr> </tr>
<tr> <tr>
<th>Minimum Costs</th> <th>Minimum Costs <sup>*</sup></th>
<td class="text-right">TBA</td> <td class="text-right">${{ is_numeric($o->minimum_cost) ? number_format($o->minimum_cost,2) : $o->minimum_cost }}</td>
</tr> </tr>
<tfoot>
<tr><td colspan="2"><sup>* Additional setup charges may apply for complex installations.</sup></td></tr>
</tfoot>
</table> </table>

View File

@ -8,23 +8,23 @@
<td class="text-right">VOIP</td> <td class="text-right">VOIP</td>
</tr> </tr>
<tr> <tr>
<th>Setup Cost</th> <th>Setup Charges</th>
<td class="text-right">TBA</td> <td class="text-right">${{ is_numeric($o->setup_cost) ? number_format($o->setup_cost,2) : $o->setup_cost }}</td>
</tr> </tr>
<tr> <tr>
<th>Monthly Cost</th> <th>Cost</th>
<td class="text-right">TBA</td> <td class="text-right">${{ is_numeric($o->default_cost) ? number_format($o->default_cost,2) : $o->default_cost }}</td>
</tr> </tr>
<tr> <tr>
<th>Default Billing</th> <th>Default Billing</th>
<td class="text-right">TBA</td> <td class="text-right">{{ is_numeric($o->default_billing) ? number_format($o->default_billing,2) : $o->default_billing }}</td>
</tr> </tr>
<tr> <tr>
<th>Contract Term</th> <th>Contract Term</th>
<td class="text-right">TBA</td> <td class="text-right">{{ $o->contract_term }} mths</td>
</tr> </tr>
<tr> <tr>
<th>Minimum Costs</th> <th>Minimum Costs</th>
<td class="text-right">TBA</td> <td class="text-right">${{ is_numeric($o->minimum_cost) ? number_format($o->minimum_cost,2) : $o->minimum_cost }}</td>
</tr> </tr>
</table> </table>

View File

@ -23,6 +23,7 @@ Route::group(['middleware'=>['auth:api','role:reseller']], function() {
Route::get('/r/agents','ResellerServicesController@agents'); Route::get('/r/agents','ResellerServicesController@agents');
Route::get('/r/accounts','ResellerServicesController@accounts'); Route::get('/r/accounts','ResellerServicesController@accounts');
Route::get('/r/clients','ResellerServicesController@clients'); Route::get('/r/clients','ResellerServicesController@clients');
Route::get('/r/service_movements','ResellerServicesController@service_movements');
}); });
Route::group(['middleware'=>'auth:api'], function() { Route::group(['middleware'=>'auth:api'], function() {