Added in email hosting, and other misc cosmetic fixes

This commit is contained in:
Deon George 2022-04-02 18:06:34 +11:00
parent 7775105da6
commit a4ed29b560
35 changed files with 1181 additions and 104 deletions

View File

@ -0,0 +1,100 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use App\Models\{Product,ProductTranslate};
class ProductController extends Controller
{
/**
* Get a list of products that meet a type
*
* @param Request $request
* @return Collection
* @throws \Exception
*/
public function api_supplier_products(Request $request): Collection
{
switch ($request->type) {
case 'App\Models\Product\Broadband':
return Product\Broadband::select(['id','supplier_broadband_id'])
->with(['supplied.supplier_detail.supplier'])
->get()
->map(function($item) { return ['id'=>$item->id,'name'=>sprintf('%s: %s',$item->supplied->supplier_detail->supplier->name,$item->supplied->name)]; })
->sortBy('name')
->values();
case 'App\Models\Product\Email':
return Product\Email::select(['id','supplier_email_id'])
->with(['supplied.supplier_detail.supplier'])
->get()
->map(function($item) { return ['id'=>$item->id,'name'=>sprintf('%s: %s',$item->supplied->supplier_detail->supplier->name,$item->supplied->name)]; })
->sortBy('name')
->values();
default:
throw new \Exception('Unknown type: '.$request->type);
}
}
/**
* Update a suppliers details
*
* @param Request $request
* @param Product $o
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
*/
public function details(Request $request,Product $o)
{
if ($request->post()) {
$validation = $request->validate([
'description.name' => 'required|string|min:2|max:255',
'active' => 'sometimes|accepted',
'model' => 'sometimes|string', // @todo Check that it is a valid model type
'model_id' => 'sometimes|int', // @todo Check that it is a valid model type
]);
foreach (collect($validation)->except('description') as $key => $item)
$o->{$key} = $item;
$o->active = (bool)$request->active;
try {
$o->save();
} catch (\Exception $e) {
return redirect()->back()->withErrors($e->getMessage())->withInput();
}
$o->load(['description']);
$oo = $o->description ?: new ProductTranslate;
foreach (collect($validation)->get('description',[]) as $key => $item)
$oo->{$key} = $item;
$o->description()->save($oo);
return redirect()->back()
->with('success','Product saved');
}
if (! $o->exists && $request->name)
$o = Product::where('name',$request->name)->firstOrNew();
return view('a.product.details')
->with('o',$o);
}
/**
* Manage products for a site
*
* @note This method is protected by the routes
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
*/
public function home()
{
return view('a.product.home');
}
}

View File

@ -255,6 +255,20 @@ class ServiceController extends Controller
->with('o',$o);
}
public function email_list(): View
{
// @todo Need to add the with path when calculating next_billed and price
$o = Service\Email::serviceActive()
->serviceUserAuthorised(Auth::user())
->select('service_emails.*')
->join('ab_service',['ab_service.id'=>'service_emails.service_id'])
->with(['service.account','service.product.type.supplied.supplier_detail.supplier','tld'])
->get();
return view('r.service.email.list')
->with('o',$o);
}
/**
* Update details about a service
*

View File

@ -9,7 +9,6 @@ use App\Models\Service;
abstract class ServiceType extends Model
{
public $timestamps = FALSE;
public $dateFormat = 'U';
/**
* @NOTE: The service_id column could be discarded, if the id column=service_id

View File

@ -2,16 +2,18 @@
namespace App\Models;
use Illuminate\Container\Container;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Leenooks\Traits\ScopeActive;
use App\Interfaces\IDs;
use App\Interfaces\{IDs,ProductItem};
use App\Traits\{ProductDetails,SiteID};
/**
@ -23,6 +25,7 @@ use App\Traits\{ProductDetails,SiteID};
*
* Attributes for products:
* + lid : Local ID for product (part number)
* + sid : System ID for product (part number)
* + supplied : Supplier product provided for this offering
* + supplier : Supplier for this offering
* + name : Brief Name for our product
@ -36,6 +39,7 @@ use App\Traits\{ProductDetails,SiteID};
* + base_charge_taxable : Default billing amount including taxes
* + min_charge : Minimum cost taking into account billing interval and setup costs
* + min_charge_taxable : Minimum cost taking into account billing interval and setup costs including taxes
* + type : Returns the underlying product object, representing the type of product
*
* Attributes for product types (type - Product/*)
* + name : Short Name for our Product
@ -66,6 +70,8 @@ class Product extends Model implements IDs
'pricing'=>'collection',
];
protected $with = ['description'];
/* RELATIONS */
/**
@ -145,7 +151,7 @@ class Product extends Model implements IDs
*/
public function getBaseCostAttribute(): float
{
return round($this->type->supplied->base_cost*Invoice::billing_change($this->type->supplied->getBillingIntervalAttribute(),$this->getBillingIntervalAttribute()) ?: 0,2);
return round($this->getSuppliedAttribute()->base_cost*Invoice::billing_change($this->getSuppliedAttribute()->getBillingIntervalAttribute(),$this->getBillingIntervalAttribute()) ?: 0,2);
}
/**
@ -167,7 +173,7 @@ class Product extends Model implements IDs
*/
public function getBillingIntervalAttribute(): int
{
return max($this->price_recur_default,$this->type->supplied->getBillingIntervalAttribute());
return max($this->price_recur_default,$this->getSuppliedAttribute()->getBillingIntervalAttribute());
}
/**
@ -239,10 +245,31 @@ class Product extends Model implements IDs
* Get our product type
*
* @return string
* @todo is the test of type and type->supplied necessary?
*/
public function getProductTypeAttribute(): string
{
return ($this->type && $this->type->supplied) ? $this->type->supplied->getTypeAttribute() : 'Unknown';
return ($this->type && $this->type->supplied) ? $this->getSuppliedAttribute()->getTypeAttribute() : 'Unknown';
}
/**
* Suppliers
*
* @return Model
*/
public function getSupplierAttribute(): Model
{
return $this->getSuppliedAttribute()->supplier_detail->supplier;
}
/**
* Suppliers product
*
* @return Model
*/
public function getSuppliedAttribute(): Model
{
return $this->type->supplied;
}
/**
@ -277,7 +304,7 @@ class Product extends Model implements IDs
*/
public function getSetupCostAttribute(): float
{
return $this->type->supplied->setup_cost ?: 0;
return $this->getSuppliedAttribute()->setup_cost ?: 0;
}
/**
@ -294,13 +321,33 @@ class Product extends Model implements IDs
/* METHODS */
/**
* Return if this product captures usage data
* Return a list of available product types
*
* @return bool
* @return Collection
*/
public function hasUsage(): bool
function availableTypes(): Collection
{
return $this->type->hasUsage();
$models = collect(File::allFiles(app_path()))
->map(function ($item) {
$path = $item->getRelativePathName();
$class = sprintf('%s%s',
Container::getInstance()->getNamespace(),
strtr(substr($path, 0, strrpos($path, '.')), '/', '\\'));
return $class;
})
->filter(function ($class) {
$valid = FALSE;
if (class_exists($class)) {
$reflection = new \ReflectionClass($class);
$valid = $reflection->isSubclassOf(ProductItem::class) && (! $reflection->isAbstract());
}
return $valid;
});
return $models->values();
}
/**
@ -347,6 +394,16 @@ class Product extends Model implements IDs
return round($price,2);
}
/**
* Return if this product captures usage data
*
* @return bool
*/
public function hasUsage(): bool
{
return $this->type->hasUsage();
}
/**
* When receiving an order, validate that we have all the required information for the product type
*

View File

@ -3,6 +3,7 @@
namespace App\Models\Product;
use Illuminate\Support\Collection;
use Leenooks\Traits\ScopeActive;
use App\Interfaces\ProductItem;
use App\Models\Supplier;
@ -11,6 +12,8 @@ use App\Models\Supplier\Broadband as SupplierBroadband;
final class Broadband extends Type implements ProductItem
{
use ScopeActive;
protected $table = 'product_broadband';
// Information required during the order process
@ -44,17 +47,6 @@ final class Broadband extends Type implements ProductItem
return $this->hasOne(SupplierBroadband::class,'id','supplier_broadband_id');
}
/**
* The supplier
*
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
*/
// @todo To check
public function supplier()
{
return $this->hasOneThrough(Supplier::class,SupplierBroadband::class,'id','id','adsl_supplier_plan_id','supplier_id');
}
/* INTERFACES */
/**

View File

@ -0,0 +1,69 @@
<?php
namespace App\Models\Product;
use Illuminate\Support\Collection;
use App\Interfaces\ProductItem;
use App\Models\Service\Email as ServiceEmail;
use App\Models\Supplier\Email as SupplierEmail;
final class Email extends Type implements ProductItem
{
protected $table = 'product_email';
// The model that is referenced when this product is ordered
protected string $order_model = ServiceEmail::class;
/* RELATIONS */
/**
* The offering supplied with this product
*
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function supplied()
{
return $this->hasOne(SupplierEmail::class,'id','supplier_email_id');
}
/* INTERFACES */
public function allowance(): Collection
{
// N/A
return collect();
}
public function allowance_string(): string
{
// N/A
return '';
}
public function getContractTermAttribute(): int
{
return 12;
}
public function getCostAttribute(): float
{
// N/A
return 0;
}
public function getSupplierAttribute()
{
return '';
}
public function getTypeAttribute()
{
return 'Domain Name';
}
public function hasUsage(): bool
{
return FALSE;
}
}

View File

@ -8,6 +8,8 @@ class ProductTranslate extends Model
{
protected $table = 'ab_product_translate';
public $timestamps = FALSE;
public function getDescriptionFullAttribute($value)
{
return unserialize($value);

View File

@ -27,6 +27,7 @@ use App\Interfaces\IDs;
* + additional_cost : Pending additional charges for this service (excluding setup)
* + billing_cost : Charge for this service each invoice period
* + billing_interval : The period that this service is billed for by default
* + billing_interval_string : The period that this service is billed for by default as a name
* + name : Service short name with service address
* + name_short : Service Product short name, eg: phone number, domain name, certificate CN
* + name_detail : Service Detail, eg: service_address
@ -880,11 +881,11 @@ class Service extends Model implements IDs
* This is used for view specific details
*
* @return string
* @todo I think this can be removed - and dynamically determined
*/
public function getSTypeAttribute(): string
{
switch($this->product->model) {
case 'App\Models\Product\Broadband': return 'broadband';
default: return $this->type->type;
}
}

View File

@ -10,6 +10,7 @@ class AdslTraffic extends Model
protected $table = 'ab_service__adsl_traffic';
public $timestamps = FALSE;
protected $dates = ['date'];
public $dateFormat = 'U';
private $traffic_end = 14;
public function broadband()

View File

@ -22,6 +22,7 @@ class Broadband extends ServiceType implements ServiceItem,ServiceUsage
'service_connect_date',
'service_contract_date'
];
public $dateFormat = 'U';
protected $table = 'ab_service__adsl';
/* RELATIONS */

View File

@ -27,46 +27,11 @@ class Domain extends ServiceType implements ServiceItem
protected $dates = [
'domain_expire',
];
public $dateFormat = 'U';
protected $table = 'service_domains';
protected $with = ['tld'];
/* RELATIONS */
public function account()
{
return $this->hasOneThrough(Account::class,Service::class);
}
public function registrar()
{
return $this->belongsTo(DomainRegistrar::class,'domain_registrar_id');
}
public function tld()
{
return $this->belongsTo(DomainTld::class,'domain_tld_id');
}
/* SCOPES */
/**
* Search for a record
*
* @param $query
* @param string $term
* @return mixed
*/
public function scopeSearch($query,string $term)
{
// If we have a period in the name, we'll ignore everything after it.
$term = strstr($term,'.',TRUE) ?: $term;
// Build our where clause
return parent::scopeSearch($query,$term)
->orwhere('domain_name','like','%'.$term.'%');
}
/* ATTRIBUTES */
/* INTERFACES */
public function getServiceDescriptionAttribute(): string
{
@ -93,4 +58,41 @@ class Domain extends ServiceType implements ServiceItem
{
return $this->domain_expire->isFuture();
}
/* RELATIONS */
public function account()
{
return $this->hasOneThrough(Account::class,Service::class);
}
public function registrar()
{
return $this->belongsTo(DomainRegistrar::class,'domain_registrar_id');
}
public function tld()
{
return $this->belongsTo(DomainTld::class,'domain_tld_id');
}
/* SCOPES */
/**
* Search for a record
*
* @param $query
* @param string $term
* @return mixed
*/
public function scopeSearch($query,string $term)
{
// If we have a period in the name, we'll ignore everything after it.
$term = strstr($term,'.',TRUE) ?: $term;
// Build our where clause
return parent::scopeSearch($query,$term)
->orwhere('domain_name','like','%'.$term.'%');
}
/* ATTRIBUTES */
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Models\Service;
use Carbon\Carbon;
use App\Models\Base\ServiceType;
use App\Models\{Account, DomainRegistrar, DomainTld, Service, TLD};
use App\Interfaces\ServiceItem;
use App\Traits\{NextKey,ScopeServiceActive,ScopeServiceUserAuthorised};
/**
* Class Email (Service)
* Services that email hostings
*
* Attributes for services:
* + service_description : Description as shown in a Service Context
* + service_expire : The date the service expires
* + service_name : Name as shown in a Service Context
*
* @package App\Models\Service
*/
class Email extends ServiceType implements ServiceItem
{
use ScopeServiceActive,ScopeServiceUserAuthorised;
protected $dates = ['expire_at'];
protected $table = 'service_emails';
/* INTERFACES */
public function getServiceDescriptionAttribute(): string
{
// N/A
return 'Email Hosting';
}
public function getServiceExpireAttribute(): Carbon
{
return $this->expire_at ?: $this->service->next_invoice;
}
/**
* The name of the domain with its TLD
*
* @return string
* // @todo
*/
public function getServiceNameAttribute(): string
{
return strtoupper(sprintf('%s.%s',$this->domain_name,$this->tld->name));
}
public function inContract(): bool
{
return $this->expire_at && $this->expire_at->isFuture();
}
/* RELATIONS */
public function tld()
{
return $this->belongsTo(TLD::class);
}
}

View File

@ -17,6 +17,7 @@ class Host extends ServiceType implements ServiceItem
protected $dates = [
'host_expire',
];
public $dateFormat = 'U';
protected $table = 'ab_service__hosting';
public function provider()

View File

@ -16,6 +16,7 @@ class Voip extends ServiceType implements ServiceItem
'service_connect_date',
'service_contract_date',
];
public $dateFormat = 'U';
protected $table = 'ab_service__voip';
/* SCOPES */

View File

@ -7,7 +7,7 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Leenooks\Traits\ScopeActive;
use App\Models\Supplier\{Broadband,Domain,Ethernet,Generic,Host,HSPA,Voip};
use App\Models\Supplier\{Broadband,Domain,Email,Ethernet,Generic,Host,HSPA,Voip};
class Supplier extends Model
{
@ -33,6 +33,10 @@ class Supplier extends Model
'name' => 'Domain Name',
'class' => Domain::class,
],
'email' => [
'name' => 'Email Hosting',
'class' => Email::class,
],
'generic' => [
'name' => 'Generic',
'class' => Generic::class,

View File

@ -12,11 +12,6 @@ final class Domain extends Type implements SupplierItem
/* INTERFACES */
public function types()
{
return $this->belongsToMany(ProductDomain::class,$this->table,'id','id','id',$this->table.'_id');
}
public function getBillingIntervalAttribute(): int
{
return 4; // Yearly
@ -27,6 +22,11 @@ final class Domain extends Type implements SupplierItem
return sprintf('%s: %s',$this->product_id,$this->tld->name);
}
public function types()
{
return $this->belongsToMany(ProductDomain::class,$this->table,'id','id','id',$this->table.'_id');
}
/* RELATIONS */
public function tld()

View File

@ -0,0 +1,23 @@
<?php
namespace App\Models\Supplier;
use App\Interfaces\SupplierItem;
use App\Models\Product\Email as ProductEmail;
final class Email extends Type implements SupplierItem
{
protected $table = 'supplier_email';
/* INTERFACES */
public function getBillingIntervalAttribute(): int
{
return 4; // Yearly
}
public function types()
{
return $this->belongsToMany(ProductEmail::class,$this->table,'id','id','id',$this->table.'_id');
}
}

View File

@ -0,0 +1,106 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class EmailSupplier extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('supplier_email', function (Blueprint $table) {
$table->integer('id',TRUE)->unsigned();
$table->timestamps();
$table->integer('site_id')->unsigned();
$table->boolean('active');
$table->integer('supplier_detail_id')->unsigned();
$table->string('product_id');
$table->string('product_desc')->nullable();
$table->float('base_cost');
$table->float('setup_cost')->nullable();
$table->integer('contract_term')->nullable();
$table->foreign(['supplier_detail_id'])->references(['id'])->on('supplier_details');
$table->foreign(['site_id'])->references(['id'])->on('sites');
});
Schema::create('product_email', function (Blueprint $table) {
$table->integer('id',TRUE)->unsigned();
$table->timestamps();
$table->integer('site_id')->unsigned();
$table->boolean('active');
$table->integer('supplier_email_id')->unsigned();
$table->string('product_id');
$table->string('product_desc')->nullable();
$table->float('base_cost');
$table->float('setup_cost')->nullable();
$table->integer('contract_term')->nullable();
$table->foreign(['supplier_email_id'])->references(['id'])->on('supplier_email');
$table->foreign(['site_id'])->references(['id'])->on('sites');
});
Schema::create('service_emails', function (Blueprint $table) {
$table->integer('id',TRUE)->unsigned();
$table->integer('site_id')->unsigned();
$table->integer('service_id')->unsigned();
$table->integer('tld_id')->unsigned();
$table->string('domain_name',128);
$table->date('expire_at')->nullable();
$table->string('admin_url')->nullable();
$table->string('admin_user')->nullable();
$table->string('admin_pass')->nullable();
$table->integer('accounts')->nullable();
$table->foreign(['site_id'])->references(['id'])->on('sites');
$table->foreign(['service_id'])->references(['id'])->on('ab_service');
$table->foreign(['tld_id'])->references(['id'])->on('tlds');
});
// Migrate email hosting from hosting table to service_email
// Setup Domains
foreach (\Illuminate\Support\Facades\DB::select("SELECT * FROM ab_service__hosting WHERE host_type='email'") as $o) {
$oo = new \App\Models\Service\Email;
foreach (['site_id','service_id'] as $item)
$oo->{$item} = $o->{$item};
$oo->tld_id = $o->domain_tld_id;
$oo->domain_name = strtolower($o->domain_name);
$oo->admin_user = strtolower($o->host_username);
$oo->admin_pass = $o->host_password;
$oo->expire_at = \Carbon\Carbon::createFromTimestamp($o->host_expire);
$oo->save();
\App\Models\Service::where('id',$o->service_id)->update(['model'=>get_class($oo)]);
};
\Illuminate\Support\Facades\DB::select("DELETE FROM ab_service__hosting WHERE host_type='email'");
// insert into suppliers value (null,1,'Google',null,null,null,null,null);
// insert into supplier_details values (null,now(),now(),null,null,null,null,14,1,null);
// insert into supplier_email values (null,now(),now(),1,1,13,'Legacy Email','Legacy Email',0,0,0);
// insert into product_email values (null,now(),now(),1,1,1,'Email Hosting',null,150,0,12);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
/*
Schema::dropIfExists('product_email');
Schema::dropIfExists('supplier_email');
Schema::dropIfExists('service_emails');
*/
abort(500,'cant go back');
}
}

View File

@ -0,0 +1,61 @@
@extends('adminlte::layouts.app')
@section('htmlheader_title')
{{ $o->name ?: 'New Product' }}
@endsection
@section('page_title')
{{ $o->name ?: 'New Product' }}
@endsection
@section('contentheader_title')
{{ $o->name ?: 'New Product' }}
@endsection
@section('contentheader_description')
@endsection
@section('main-content')
<div class="row">
<div class="col-12">
@include('adminlte::widget.status')
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header bg-dark d-flex p-0">
<ul class="nav nav-pills w-100 p-2">
<li class="nav-item"><a class="nav-link active" href="#details" data-toggle="tab">Detail</a></li>
{{--
<li class="nav-item"><a class="nav-link" href="#products" data-toggle="tab">Products</a></li>
<li class="nav-item"><a class="nav-link" href="#offerings" data-toggle="tab">Offerings</a></li>
<li class="nav-item"><a class="nav-link" href="#connections" data-toggle="tab">Connections</a></li>
--}}
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<div class="tab-pane fade active show" id="details" role="tabpanel">
@include('a.product.widgets.detail')
</div>
{{--
<div class="tab-pane fade" id="products" role="tabpanel">
@include('a.product.widgets.products')
</div>
<div class="tab-pane fade" id="offerings" role="tabpanel">
@include('a.product.widgets.offerings')
</div>
<div class="tab-pane fade" id="connections" role="tabpanel">
@include('a.product.widgets.connections')
</div>
--}}
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,70 @@
@extends('adminlte::layouts.app')
@section('htmlheader_title')
Product
@endsection
@section('page_title')
Product
@endsection
@section('contentheader_title')
Product
@endsection
@section('contentheader_description')
@endsection
@section('main-content')
<div class="row">
<div class="col-12">
<div class="card card-dark">
<div class="card-header">
<h1 class="card-title">Product Configuration</h1>
</div>
<div class="card-body">
<form class="g-0 needs-validation" method="POST" enctype="multipart/form-data" role="form">
@csrf
<div class="row">
<div class="col-4">
<div class="form-group has-validation">
<label for="name">Product Name</label>
<select class="form-control form-control-border" id="name" name="supplier_id">
<option value=""></option>
<option value="">Add New</option>
@foreach(\App\Models\Product::get()->sortBy('name') as $o)
<option value="{{ $o->id }}">{{ $o->name }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('name')
{{ $message }}
@else
Product Name is required.
@enderror
</span>
<span class="input-helper">Product Name</span>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection
@section('page-scripts')
@css(select2)
@js(select2,autofocus)
<script type="text/javascript">
$(document).ready(function() {
$('#name').select2()
.on('change',function(item) {
window.location.href = '{{ url('a/product/details') }}/'+item.target.value;
});
});
</script>
@endsection

View File

@ -0,0 +1,155 @@
<!-- $o = Product::class -->
<div class="row">
<div class="col-12">
<h3>Product Details</h3>
<hr>
@if(session()->has('success'))
<span class="ml-3 pt-0 pb-0 pr-1 pl-1 btn btn-outline-success"><small>{{ session()->get('success') }}</small></span>
@endif
<form class="g-0 needs-validation" method="POST" enctype="multipart/form-data" role="form">
@csrf
<div class="row">
<div class="col-4">
<div class="row">
<!-- Product Name -->
<div class="col-9">
<div class="form-group has-validation">
<label for="name">Product Name</label>
<input type="text" class="form-control form-control-border @error('description.name') is-invalid @enderror" id="name" name="description[name]" placeholder="Product Name" value="{{ old('description.name',$o->name) }}" required>
<span class="invalid-feedback" role="alert">
@error('description.name')
{{ $message }}
@else
Product Name required.
@enderror
</span>
</div>
</div>
<!-- Product Active -->
<div class="col-3">
<div class="form-group">
<div class="custom-control custom-switch custom-switch-off-danger custom-switch-on-success">
<input type="checkbox" class="custom-control-input" id="active" name="active" {{ old('active',$o->active) ? 'checked' : '' }}>
<label class="custom-control-label" for="active">Active</label>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Product Type -->
<div class="col-4">
<div class="form-group has-validation">
<label for="name">Product Type</label>
<select class="form-control form-control-border" id="model" name="model">
<option value=""></option>
@foreach($o->availableTypes() as $type)
<option value="{{ $type }}" @if($o->type && ($type == get_class($o->type)))selected @endif>{{ $type }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('model')
{{ $message }}
@else
Product Type required.
@enderror
</span>
</div>
</div>
</div>
<div class="row">
<!-- Supplied Product -->
<div class="col-4" id="supplier_product">
<div class="form-group has-validation">
<label for="name">Supplied Product</label>
<select class="form-control form-control-border" id="model_id" name="model_id">
</select>
<span class="invalid-feedback" role="alert">
@error('model_id')
{{ $message }}
@else
Supplied Product required.
@enderror
</span>
</div>
</div>
</div>
<div class="row">
<!-- Buttons -->
<div class="col-12">
<a href="{{ url('/home') }}" class="btn btn-danger">Cancel</a>
@can('wholesaler')
<button type="submit" name="submit" class="btn btn-success mr-0 float-right">@if ($o->exists)Save @else Add @endif</button>
@endcan
</div>
</div>
</form>
</div>
</div>
@section('page-scripts')
@css(select2)
@js(select2,autofocus)
<script type="text/javascript">
// Get a list of supplier items matching this type to populate model_id
function supplier_products(type,destination,selected) {
destination.prop('disabled',true);
$.ajax({
type: 'GET',
dataType: 'json',
data: {type: type},
cache: false,
url: '{{ url('api/a/supplier_products') }}',
timeout: 2000,
error: function(x) {
// @todo add a spinner
//spinner.toggleClass('d-none').toggleClass('fa-spin');
alert('Failed to submit');
},
success: function(data) {
destination
.empty()
.append($('<option>'))
.append(data.map(function(item,key) {
return new Option(item.name,item.id,selected && selected==item.id,selected && selected==item.id);
}))
.prop('disabled',false);
}
});
}
$(document).ready(function() {
$('#model').select2()
.on('change',function(item) {
if ($(this).val()) {
$('#supplier_product').show();
supplier_products($(this).val(),$('#model_id'));
} else {
$('#supplier_product').hide();
}
});
$('#model_id').select2();
// After we render the page, hide the supplier_product if this product has no model.
// We do this here, because adding d-none to the div results in the select2 input not presenting correctly
if (! $('#model').val())
$('#supplier_product').hide();
else
supplier_products($('#model').val(),$('#model_id'),{{ old('model_id',$o->model_id) }});
});
</script>
@endsection

View File

@ -6,18 +6,18 @@
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-globe-asia"></i></span>
</div>
<input type="text" class="form-control col-9 text-right @error('service.domain_name') is-invalid @enderror" id="domain_name" name="service[domain_name]" placeholder="Domain Name..." value="{{ old('service.domain_name',$o->domain_name) }}" required>
<input type="text" class="form-control text-right @error('domain.domain_name') is-invalid @enderror" id="domain_name" name="domain[domain_name]" placeholder="Domain Name..." value="{{ old('domain.domain_name',$o->domain_name) }}" required>
<div class="input-group-append">
<span class="input-group-text">.</span>
</div>
<select class="form-control col-3" name="service[domain_tld_id]">
<select class="form-control" id="domain_tld_id" name="domain[domain_tld_id]">
@foreach(\App\Models\DomainTld::active()->orderBy('name')->get() as $oo)
<option value="{{ $oo->id }}" @if($oo->id == old('service.domain_tld_id',$o->domain_tld_id))selected @endif>{{ $oo->name }}</option>
<option value="{{ $oo->id }}" @if($oo->id == old('domain.domain_tld_id',$o->domain_tld_id))selected @endif>{{ $oo->name }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('service.domain_name')
@error('domain.domain_name')
{{ $message }}
@else
Domain Name is required.
@ -35,9 +35,9 @@
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-calendar"></i></span>
</div>
<input type="date" class="form-control @error('service.domain_expire') is-invalid @enderror" id="domain_expire" name="service[domain_expire]" value="{{ old('service.domain_expire',$o->service_expire->format('Y-m-d')) }}" required>
<input type="date" class="form-control @error('domain.domain_expire') is-invalid @enderror" id="domain_expire" name="domain[domain_expire]" value="{{ old('domain.domain_expire',$o->service_expire->format('Y-m-d')) }}" required>
<span class="invalid-feedback" role="alert">
@error('service.domain_expire')
@error('domain.domain_expire')
{{ $message }}
@else
Domain Expiry is required.
@ -57,15 +57,15 @@
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-handshake"></i></span>
</div>
<select class="form-control @error('service.domain_registrar_id') is-invalid @enderror" id="domain_registrar_id" name="service[domain_registrar_id]">
<select class="form-control @error('domain.domain_registrar_id') is-invalid @enderror" id="domain_registrar_id" name="domain[domain_registrar_id]">
<option></option>
@foreach(\App\Models\DomainRegistrar::active()->orderBy('name')->get() as $oo)
<option value="{{ $oo->id }}" @if($oo->id == old('service.domain_registrar_id',$o->domain_registrar_id))selected @endif>{{ $oo->name }}</option>
<option value="{{ $oo->id }}" @if($oo->id == old('domain.domain_registrar_id',$o->domain_registrar_id))selected @endif>{{ $oo->name }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('service.domain_registrar_id')
@error('domain.domain_registrar_id')
{{ $message }}
@else
Domain Registrar is required.
@ -81,11 +81,11 @@
<label for="registrar_account">Registrar Account</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-calendar"></i></span>
<span class="input-group-text"><i class="fas fa-fw fa-user"></i></span>
</div>
<input type="text" class="form-control @error('service.registrar_account') is-invalid @enderror" id="registrar_account" name="service[registrar_account]" value="{{ old('service.registrar_account',$o->registrar_account) }}">
<input type="text" class="form-control @error('domain.registrar_account') is-invalid @enderror" id="registrar_account" name="domain[registrar_account]" value="{{ old('domain.registrar_account',$o->registrar_account) }}">
<span class="invalid-feedback" role="alert">
@error('service.registrar_account')
@error('domain.registrar_account')
{{ $message }}
@else
Registrar Account ID is required.
@ -105,13 +105,13 @@
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-project-diagram"></i></span>
</div>
<select class="form-control @error('service.registrar_ns') is-invalid @enderror" id="registrar_ns" name="service[registrar_ns]">
<select class="form-control @error('domain.registrar_ns') is-invalid @enderror" id="registrar_ns" name="domain[registrar_ns]">
@foreach(\App\Models\Service\Domain::select('registrar_ns')->distinct()->get() as $oo)
<option value="{{ $oo->registrar_ns }}" @if($oo->registrar_ns == old('service.registrar_ns',$o->registrar_ns))selected @endif>{{ $oo->registrar_ns }}</option>
<option value="{{ $oo->registrar_ns }}" @if($oo->registrar_ns == old('domain.registrar_ns',$o->registrar_ns))selected @endif>{{ $oo->registrar_ns }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('service.registrar_ns')
@error('domain.registrar_ns')
{{ $message }}
@else
DNS Details is required.
@ -127,11 +127,11 @@
<label for="registrar_username">Registrar Username</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-calendar"></i></span>
<span class="input-group-text"><i class="fas fa-fw fa-user"></i></span>
</div>
<input type="text" class="form-control @error('service.registrar_username') is-invalid @enderror" id="registrar_username" name="service[registrar_username]" value="{{ old('service.registrar_username',$o->registrar_username) }}">
<input type="text" class="form-control @error('domain.registrar_username') is-invalid @enderror" id="registrar_username" name="domain[registrar_username]" value="{{ old('domain.registrar_username',$o->registrar_username) }}">
<span class="invalid-feedback" role="alert">
@error('service.registrar_username')
@error('domain.registrar_username')
{{ $message }}
@else
Registrar Username is required.
@ -162,6 +162,13 @@
<script type="text/javascript">
$(document).ready(function() {
// @todo This is taking up too much width
//$('#domain_tld_id').select2();
$('#domain_registrar_id').select2({
dropdownAutoWidth: true,
});
$('#registrar_ns').select2({
dropdownAutoWidth: true,
width: 'style',

View File

@ -0,0 +1,148 @@
<!-- o = App\Models\Service\Email::class -->
<div class="row">
<!-- DOMAIN NAME -->
<div class="col-6">
<div class="form-group has-validation">
<label for="domain_name">Email Domain Name</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-globe-asia"></i></span>
</div>
<input type="text" class="form-control col-9 text-right @error('email.domain_name') is-invalid @enderror" id="domain_name" name="email[domain_name]" placeholder="Domain Name..." value="{{ old('email.domain_name',$o->domain_name) }}" required>
<div class="input-group-append">
<span class="input-group-text">.</span>
</div>
<select class="form-control col-3" name="email[tld_id]">
@foreach(\App\Models\TLD::orderBy('name')->get() as $oo)
<option value="{{ $oo->id }}" @if($oo->id == old('email.tld_id',$o->tld_id))selected @endif>{{ $oo->name }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('email.domain_name')
{{ $message }}
@else
Domain Name is required.
@enderror
</span>
</div>
<span class="input-helper">Domain Name</span>
</div>
</div>
<!-- EXPIRY -->
<div class="col-3">
<div class="form-group has-validation">
<label for="domain_expire">Expiry</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-calendar"></i></span>
</div>
<input type="date" class="form-control @error('email.expire_at') is-invalid @enderror" id="expire_at" name="email[expire_at]" value="{{ old('email.expire_at',($o->expire_at ? $o->expire_at->format('Y-m-d') : NULL)) }}" required>
<span class="invalid-feedback" role="alert">
@error('email.expire_at')
{{ $message }}
@else
Email hosting expiry is required.
@enderror
</span>
</div>
<span class="input-helper">Email Hosting Expires</span>
</div>
</div>
</div>
<div class="row">
<!-- ADMIN URL -->
<div class="col-12">
<div class="form-group has-validation">
<label for="domain_registrar_id">Hosting Admin URL</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fab fa-fw fa-safari"></i></span>
</div>
<input type="text" class="form-control @error('email.admin_url') is-invalid @enderror" id="admin_url" name="email[admin_url]" placeholder="Admin URL..." value="{{ old('email.admin_url',$o->admin_url) }}" required>
<span class="invalid-feedback" role="alert">
@error('email.admin_url')
{{ $message }}
@else
Admin URL is required.
@enderror
</span>
</div>
<span class="input-helper">Admin URL</span>
</div>
</div>
</div>
<div class="row">
<!-- ADMIN USER -->
<div class="col-6">
<div class="form-group has-validation">
<label for="registrar_ns">Admin User</label>
<div class="input-group flex-nowrap">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-user"></i></span>
</div>
<input type="text" class="form-control @error('email.admin_user') is-invalid @enderror" id="admin_user" name="email[admin_user]" placeholder="Admin USER" value="{{ old('email.admin_user',$o->admin_user) }}" required>
<span class="invalid-feedback" role="alert">
@error('email.admin_user')
{{ $message }}
@else
Admin USER is required.
@enderror
</span>
</div>
<span class="input-helper">Admin USER</span>
</div>
</div>
<!-- ADMIN PASS -->
<div class="col-6">
<div class="form-group has-validation">
<label for="registrar_username">Registrar Username</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-fw fa-lock"></i></span>
</div>
<input type="text" class="form-control @error('email.admin_pass') is-invalid @enderror" id="admin_pass" name="email[admin_pass]" value="{{ old('email.admin_pass',$o->admin_pass) }}" required>
<span class="invalid-feedback" role="alert">
@error('email.admin_pass')
{{ $message }}
@else
Admin PASSWORD is required.
@enderror
</span>
</div>
<span class="input-helper">Admin PASSWORD</span>
</div>
</div>
</div>
@section('page-scripts')
@css(select2)
@js(select2,autofocus)
<style>
.select2-selection.select2-selection--single {
height: calc(2.25rem + 2px) !important;
}
.select2.select2-container.select2-container--default {
display: flex;
flex: 1 1 auto;
}
.select2.select2-container.select2-container--default .selection {
width: 100%;
}
</style>
<script type="text/javascript">
$(document).ready(function() {
$('#registrar_ns').select2({
dropdownAutoWidth: true,
width: 'style',
tags: true,
});
});
</script>
@append

View File

@ -22,9 +22,9 @@
<td>${{ number_format($b=\App\Models\Tax::tax_calc($o->product->setup_charge,$o->account->taxes),2) }}</td>
<td>
@if ($a > $b)
<span class="badge bg-danger>">({{ number_format(($a-$b)/($b ?: 1)*100,1) }})%</span>
<span class="badge bg-danger>">({{ number_format($a ? ($b-$a)/($b ?: 1)*100 : 100,1) }})%</span>
@else
<span class="badge">{{ number_format(($b-$a)/($a ?: 1)*100,1) }}%</span>
<span class="badge">{{ number_format($a ? ($b-$a)/($b ?: 1)*100 : ($b ? 100: 0),1) }}%</span>
@endif
</td>
</tr>
@ -40,9 +40,9 @@
<td>${{ number_format($b=\App\Models\Tax::tax_calc($o->product->getBaseChargeAttribute($o->billing_interval),$o->account->taxes),2) }}</td>
<td>
@if ($a > $b)
<span class="badge bg-danger>">({{ number_format(($a-$b)/($b ?: 1)*100,1) }})%</span>
<span class="badge bg-danger>">({{ number_format($a ? ($b-$a)/($b ?: 1)*100 : 100,1) }})%</span>
@else
<span class="badge">{{ number_format(($b-$a)/($a ?: 1)*100,1) }}%</span>
<span class="badge">{{ number_format($a ? ($b-$a)/($b ?: 1)*100 : ($b ? 100: 0),1) }}%</span>
@endif
</td>
</tr>
@ -52,9 +52,9 @@
<td>${{ number_format($b=\App\Models\Tax::tax_calc($o->product->getBaseChargeAttribute($o->billing_interval)*\App\Models\Invoice::billing_change($o->billing_interval,1),$o->account->taxes),2) }}</td>
<td>
@if ($a > $b)
<span class="badge bg-danger>">({{ number_format(($a-$b)/($b ?: 1)*100,1) }})%</span>
<span class="badge bg-danger>">({{ number_format($a ? ($b-$a)/($b ?: 1)*100 : 100,1) }})%</span>
@else
<span class="badge">{{ number_format(($b-$a)/($a ?: 1)*100,1) }}%</span>
<span class="badge">{{ number_format($a ? ($b-$a)/($b ?: 1)*100 : ($b ? 100: 0),1) }}%</span>
@endif
</td>
</tr>
@ -70,9 +70,9 @@
<td>${{ number_format($b=\App\Models\Tax::tax_calc($o->product->getMinChargeAttribute($o->billing_interval),$o->account->taxes),2) }}</td>
<td>
@if ($a > $b)
<span class="badge bg-danger>">({{ number_format(($a-$b)/($b ?: 1)*100,1) }})%</span>
<span class="badge bg-danger>">({{ number_format($a ? ($b-$a)/($b ?: 1)*100 : 100,1) }})%</span>
@else
<span class="badge">{{ number_format(($b-$a)/($a ?: 1)*100,1) }}%</span>
<span class="badge">{{ number_format($a ? ($b-$a)/($b ?: 1)*100 : ($b ? 100: 0),1) }}%</span>
@endif
</td>
</tr>

View File

@ -2,7 +2,7 @@
<div class="col-12">
<h4>Update Service details</h4>
<form class="g-0 needs-validation" method="POST" action="{{ url('a/service/edit',[ $o->id]) }}">
<form class="g-0 needs-validation" method="POST" action="{{ url('a/service/edit',[$o->id]) }}">
@csrf
@includeIf('a.service.widgets.'.$o->stype.'.update',['o'=>$o->type])

View File

@ -1,3 +1,4 @@
<!-- $o = App\Models\Service\Voip::class -->
<div class="form-group row">
<label for="reference" class="col-sm-3 col-form-label text-right">Service Number</label>
<div class="col-sm-6">
@ -32,4 +33,40 @@
<input type="text" class="form-control" name="voip[service_password]" value="{{ $o->service_password ?? '' }}">
</div>
</div>
</div>
<div class="form-group row">
<label for="reference" class="col-sm-3 col-form-label text-right">Service Connect Date</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-calendar"></i></span>
</div>
<input type="date" class="form-control" name="voip[service_connect_date]" value="{{ $o->service_connect_date ? $o->service_connect_date->format('Y-m-d') : '' }}">
</div>
</div>
</div>
<div class="form-group row">
<label for="reference" class="col-sm-3 col-form-label text-right">Service Contract Date</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-calendar"></i></span>
</div>
<input type="date" class="form-control" name="voip[service_contract_date]" value="{{ $o->service_contract_date ? $o->service_contract_date->format('Y-m-d') : '' }}">
</div>
</div>
</div>
<div class="form-group row">
<label for="reference" class="col-sm-3 col-form-label text-right">Service Billing Start Date</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-calendar"></i></span>
</div>
<input type="date" class="form-control" name="date_start" value="{{ $o->service->date_start ? $o->service->date_start->format('Y-m-d') : '' }}">
</div>
</div>
</div>

View File

@ -28,7 +28,7 @@
</div>
</div>
<!-- Supplier Name -->
<!-- Supplier Active -->
<div class="col-3">
<div class="form-group">
<div class="custom-control custom-switch custom-switch-off-danger custom-switch-on-success">
@ -113,7 +113,7 @@
<div class="col-12">
<div class="form-group has-validation">
<label for="accounts">Accounts Email</label>
<input type="accounts" class="form-control form-control-border @error('supplier_details.accounts') is-invalid @enderror" id="accounts" name="supplier_details[accounts]" placeholder="Accounts Email" value="{{ old('supplier_details.accounts',($o->detail ? $o->detail->accounts : '')) }}">
<input type="text" class="form-control form-control-border @error('supplier_details.accounts') is-invalid @enderror" id="accounts" name="supplier_details[accounts]" placeholder="Accounts Email" value="{{ old('supplier_details.accounts',($o->detail ? $o->detail->accounts : '')) }}">
<span class="invalid-feedback" role="alert">
@error('supplier_details.accounts')
{{ $message }}
@ -128,7 +128,7 @@
<div class="col-12">
<div class="form-group has-validation">
<label for="support">Support Email</label>
<input type="support" class="form-control form-control-border @error('supplier_details.support') is-invalid @enderror" id="support" name="supplier_details[support]" placeholder="Support Email" value="{{ old('supplier_details.support',($o->detail ? $o->detail->support : '')) }}">
<input type="text" class="form-control form-control-border @error('supplier_details.support') is-invalid @enderror" id="support" name="supplier_details[support]" placeholder="Support Email" value="{{ old('supplier_details.support',($o->detail ? $o->detail->support : '')) }}">
<span class="invalid-feedback" role="alert">
@error('supplier_details.support')
{{ $message }}
@ -143,7 +143,7 @@
<div class="col-12">
<div class="form-group has-validation">
<label for="payments">Payment Details</label>
<input type="payments" class="form-control form-control-border @error('supplier_details.payments') is-invalid @enderror" id="payments" name="supplier_details[payments]" placeholder="Payment Details" value="{{ old('supplier_details.payments',($o->detail ? $o->detail->payments : '')) }}">
<input type="text" class="form-control form-control-border @error('supplier_details.payments') is-invalid @enderror" id="payments" name="supplier_details[payments]" placeholder="Payment Details" value="{{ old('supplier_details.payments',($o->detail ? $o->detail->payments : '')) }}">
<span class="invalid-feedback" role="alert">
@error('supplier_details.payments')
{{ $message }}

View File

@ -45,7 +45,7 @@
<td>{{ $oo->registrar_ns }}</td>
<td>@if ($oo->service->isBilled()) <span class="@if($oo->service->suspend_billing)strike @endif">{{ $oo->service->invoice_next->format('Y-m-d') }}</span> @else - @endif</td>
<td>@if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items(TRUE)->sum('total'),2) }}@else - @endif</td>
<td>{{ $oo->service->billing_period }}</td>
<td>{{ $oo->service->billing_interval_string }}</td>
</tr>
@endforeach
</tbody>

View File

@ -0,0 +1,90 @@
@extends('adminlte::layouts.app')
@section('htmlheader_title')
Email Hosting
@endsection
@section('page_title')
Email Hosting
@endsection
@section('contentheader_title')
Email Hosting
@endsection
@section('contentheader_description')
Email Hosting currently managed.
@endsection
@section('main-content')
<div class="row">
<div class="col-12">
<div class="card card-dark">
<div class="card-body">
<table class="table table-hover" id="service_domain">
<thead>
<tr>
<th>Service ID</tH>
<th>Account</th>
<th>Domain</th>
<th>Expires</th>
<th>Supplier</th>
<th>Admin URL</th>
<th>Admin Details</th>
<th>Next Billed</th>
<th>Price</th>
<th>Term</th>
</tr>
</thead>
<tbody>
@foreach ($o as $oo)
<tr @if ($oo->service_expire->isPast()) class="table-danger" @endif>
<td><a href="{{ url('u/service',[$oo->service_id]) }}">{{ $oo->service->sid }}</td>
<td>{{ $oo->service->account->name }}</td>
<td>{{ $oo->service_name }}</td>
<td>{{ $oo->service_expire->format('Y-m-d') }}</td>
<td>{{ $oo->service->product->supplier->name }}</td>
<td>{{ $oo->admin_url }}</td>
<td>@if($oo->admin_user){{ $oo->admin_user }}/{{ $oo->admin_pass }}@else &nbsp; @endif</td>
<td>@if ($oo->service->isBilled()) <span class="@if($oo->service->suspend_billing)strike @endif">{{ $oo->service->invoice_next->format('Y-m-d') }}</span> @else - @endif</td>
<td>@if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items(TRUE)->sum('total'),2) }}@else - @endif</td>
<td>{{ $oo->service->billing_interval_string }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
@endsection
@section('page-scripts')
@css(datatables,bootstrap4|rowgroup)
@js(datatables,bootstrap4|rowgroup)
<style>
.strike {
text-decoration: line-through;
}
</style>
<script type="text/javascript">
$(document).ready(function() {
$('#service_domain').DataTable({
order: [[4,'asc'],[1,'asc'],[2,'desc']],
rowGroup: {
dataSrc: 4,
},
columnDefs: [
{
targets: [4],
visible: false,
}
],
});
$('#invoices_due tbody').on('click','tr', function () {
$(this).toggleClass('selected');
});
});
</script>
@append

View File

@ -42,7 +42,7 @@
<li class="nav-item"><a class="nav-link" href="#emails" data-toggle="tab">Emails</a></li>
--}}
@can('wholesaler')
<li class="nav-item ml-auto"><a class="nav-link" href="#internal" data-toggle="tab">Billing History</a></li>
<li class="nav-item ml-auto"><a class="nav-link" href="#billing" data-toggle="tab">Billing History</a></li>
<li class="nav-item"><a class="nav-link" href="#internal" data-toggle="tab">Internal</a></li>
<li class="nav-item"><a class="nav-link {{ session()->has('service_update') ? 'active' : '' }}" href="#update" data-toggle="tab">Update</a></li>
@endcan

View File

@ -0,0 +1,45 @@
<!-- $o = App\Models\Service\Host::class -->
{{-- @todo Change this to App\Models\Service\Email --}}
<div class="card">
@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">Email Hosting Details</h3>
</div>
<div class="card-body bg-gray-dark">
<table class="table table-sm">
<tr>
<th>Domain Name</th>
<td>{{ $o->service_name }}</td>
</tr>
@if($o->service_connect_date)
<tr>
<th>Connected</th>
<td>{{ $o->service_connect_date->format('Y-m-d') }}</td>
</tr>
@endif
@if ($o->inContract())
<tr>
<th>Contract</th>
<!-- @todo -->
<td>12 months <small>({{ ($x=$o->domain_expire)->diffForHumans() }})</small></td>
</tr>
<tr>
<th>Contract End</th>
<td>{{ $x->format('Y-m-d') }}</td>
</tr>
@endif
<tr>
<th>Cancel Notice</th>
<td>Before renewal</td>
</tr>
</table>
</div>
</div>

View File

@ -15,7 +15,7 @@
<table class="table table-sm">
<tr>
<th>Account</th>
<td>{{ $o->account->aid }}</td>
<td><a href="{{url('u/home/',$o->account_id)}}">{{ $o->account->aid }}</a></td>
</tr>
<tr>
<th>Status</th>

View File

@ -77,6 +77,12 @@
</a>
</li>
<li class="nav-item">
<a href="{{ url('a/product') }}" class="nav-link @if(preg_match('#^a/product]#',Route::current()->uri())) active @endif">
<i class="nav-icon fas fa-barcode"></i> <p>Products</p>
</a>
</li>
<li class="nav-item">
<a href="{{ url('a/supplier') }}" class="nav-link @if(preg_match('#^a/supplier#',Route::current()->uri())) active @endif">
<i class="nav-icon fas fa-user-tag"></i> <p>Suppliers</p>
@ -101,8 +107,8 @@
@can('reseller')
<li class="nav-header">RESELLER</li>
<li class="nav-item has-treeview @if(preg_match('#^a/report/(domain)#',request()->path()))menu-open @else menu-closed @endif">
<a href="#" class="nav-link @if(preg_match('#^a/report/(domain)#',request()->path())) active @endif">
<li class="nav-item has-treeview @if(preg_match('#^r/report/(domain|email)#',request()->path()))menu-open @else menu-closed @endif">
<a href="#" class="nav-link @if(preg_match('#^r/report/(domain|email)#',request()->path())) active @endif">
<i class="nav-icon fas fa-list"></i> <p>REPORT<i class="fas fa-angle-left right"></i></p>
</a>
@ -112,6 +118,12 @@
<i class="nav-icon fas fa-globe-asia"></i> <p>Domain Names</p>
</a>
</li>
<li class="nav-item">
<a href="{{ url('r/report/email') }}" class="nav-link @if(preg_match('#^r/report/email#',request()->path()))active @endif">
<i class="nav-icon fas fa-envelope"></i> <p>Email Hosting</p>
</a>
</li>
</ul>
</li>
@endcan

View File

@ -2,6 +2,7 @@
use App\Http\Controllers\{AdminController,
CheckoutController,
ProductController,
ResellerServicesController};
/*
@ -15,6 +16,12 @@ use App\Http\Controllers\{AdminController,
|
*/
// Wholesaler API calls
Route::group(['middleware'=>['auth:api','role:wholesaler']], function() {
Route::get('a/supplier_products',[ProductController::class,'api_supplier_products']);
});
// Reseller API calls
Route::group(['middleware'=>['auth:api','role:reseller']], function() {
// Route::get('/r/agents','ResellerServicesController@agents');
Route::get('/r/accounts',[ResellerServicesController::class,'accounts']);

View File

@ -9,6 +9,7 @@ use App\Http\Controllers\{AdminController,
MediaController,
OrderController,
PaypalController,
ProductController,
SearchController,
ServiceController,
WelcomeController};
@ -25,8 +26,9 @@ use App\Http\Controllers\{AdminController,
*/
Auth::routes();
Route::get('/logout',[LoginController::class,'logout']);
Route::get('logout',[LoginController::class,'logout']);
// Account linking to OPENID host
Route::group(['middleware'=>['theme:adminlte-be']],function() {
Route::get('auth/{socialProvider}',[SocialLoginController::class,'redirectToProvider']);
Route::get('auth/{socialProvider}/callback',[SocialLoginController::class,'handleProviderCallback']);
@ -37,13 +39,17 @@ Route::group(['middleware'=>['theme:adminlte-be']],function() {
// Generic Image Renderer - Render images that we dont have with a generic image
Route::get('image/generic/{width}/{height}/{color}/{name?}',[MediaController::class,'image'])->name('image');
// Our Admin Routes
// Our Admin Routes - for wholesalers
Route::group(['middleware'=>['theme:adminlte-be','auth','role:wholesaler'],'prefix'=>'a'],function() {
// Site Setup
Route::match(['get','post'],'setup',[AdminController::class,'setup']);
// Product Setup
Route::match(['get'],'product',[ProductController::class,'home']);
Route::match(['get','post'],'product/details/{o?}',[ProductController::class,'details']);
// Supplier Setup
// @todo Move to Supplier Controller
Route::match(['get'],'supplier',[AdminController::class,'supplier']);
Route::match(['get','post'],'supplier/addedit/{o?}',[AdminController::class,'supplier_addedit']);
Route::match(['get','post'],'supplier/details/{o?}',[AdminController::class,'supplier_addedit']);
// Route::get('service/{o}','AdminHomeController@service');
// Route::post('service/{o}','AdminHomeController@service_update');
@ -74,6 +80,7 @@ Route::group(['middleware'=>['theme:adminlte-be','auth','role:reseller'],'prefix
Route::group(['middleware'=>['theme:adminlte-be','auth','role:reseller'],'prefix'=>'report'],function() {
Route::get('domain',[ServiceController::class,'domain_list']);
Route::get('email',[ServiceController::class,'email_list']);
});
// Charges on an account