diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php new file mode 100644 index 0000000..8eb09c2 --- /dev/null +++ b/app/Http/Controllers/ProductController.php @@ -0,0 +1,100 @@ +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'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/ServiceController.php b/app/Http/Controllers/ServiceController.php index 7b563dc..7d44e40 100644 --- a/app/Http/Controllers/ServiceController.php +++ b/app/Http/Controllers/ServiceController.php @@ -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 * diff --git a/app/Models/Base/ServiceType.php b/app/Models/Base/ServiceType.php index 2d6310f..9fa7636 100644 --- a/app/Models/Base/ServiceType.php +++ b/app/Models/Base/ServiceType.php @@ -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 diff --git a/app/Models/Product.php b/app/Models/Product.php index 7414a24..11d6bea 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -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 * diff --git a/app/Models/Product/Broadband.php b/app/Models/Product/Broadband.php index 75be01f..b9ec9d5 100644 --- a/app/Models/Product/Broadband.php +++ b/app/Models/Product/Broadband.php @@ -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 */ /** diff --git a/app/Models/Product/Email.php b/app/Models/Product/Email.php new file mode 100644 index 0000000..8198c7f --- /dev/null +++ b/app/Models/Product/Email.php @@ -0,0 +1,69 @@ +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; + } +} \ No newline at end of file diff --git a/app/Models/ProductTranslate.php b/app/Models/ProductTranslate.php index 3f629b0..83da067 100644 --- a/app/Models/ProductTranslate.php +++ b/app/Models/ProductTranslate.php @@ -8,6 +8,8 @@ class ProductTranslate extends Model { protected $table = 'ab_product_translate'; + public $timestamps = FALSE; + public function getDescriptionFullAttribute($value) { return unserialize($value); diff --git a/app/Models/Service.php b/app/Models/Service.php index 5779f4f..7516537 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -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; } } diff --git a/app/Models/Service/AdslTraffic.php b/app/Models/Service/AdslTraffic.php index 34814da..697237d 100644 --- a/app/Models/Service/AdslTraffic.php +++ b/app/Models/Service/AdslTraffic.php @@ -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() diff --git a/app/Models/Service/Broadband.php b/app/Models/Service/Broadband.php index 06cc33b..ad0288e 100644 --- a/app/Models/Service/Broadband.php +++ b/app/Models/Service/Broadband.php @@ -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 */ diff --git a/app/Models/Service/Domain.php b/app/Models/Service/Domain.php index 339c00e..c99ae99 100644 --- a/app/Models/Service/Domain.php +++ b/app/Models/Service/Domain.php @@ -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 */ } \ No newline at end of file diff --git a/app/Models/Service/Email.php b/app/Models/Service/Email.php new file mode 100644 index 0000000..abfb661 --- /dev/null +++ b/app/Models/Service/Email.php @@ -0,0 +1,65 @@ +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); + } +} \ No newline at end of file diff --git a/app/Models/Service/Host.php b/app/Models/Service/Host.php index 9594857..0aacb5e 100644 --- a/app/Models/Service/Host.php +++ b/app/Models/Service/Host.php @@ -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() diff --git a/app/Models/Service/Voip.php b/app/Models/Service/Voip.php index b26b5c6..1aef1b4 100644 --- a/app/Models/Service/Voip.php +++ b/app/Models/Service/Voip.php @@ -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 */ diff --git a/app/Models/Supplier.php b/app/Models/Supplier.php index ce0fefa..4cbdbe4 100644 --- a/app/Models/Supplier.php +++ b/app/Models/Supplier.php @@ -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, diff --git a/app/Models/Supplier/Domain.php b/app/Models/Supplier/Domain.php index a80ce0f..ab85783 100644 --- a/app/Models/Supplier/Domain.php +++ b/app/Models/Supplier/Domain.php @@ -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() diff --git a/app/Models/Supplier/Email.php b/app/Models/Supplier/Email.php new file mode 100644 index 0000000..6602f5c --- /dev/null +++ b/app/Models/Supplier/Email.php @@ -0,0 +1,23 @@ +belongsToMany(ProductEmail::class,$this->table,'id','id','id',$this->table.'_id'); + } +} \ No newline at end of file diff --git a/database/migrations/2022_04_02_092021_email_supplier.php b/database/migrations/2022_04_02_092021_email_supplier.php new file mode 100644 index 0000000..8850d13 --- /dev/null +++ b/database/migrations/2022_04_02_092021_email_supplier.php @@ -0,0 +1,106 @@ +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'); + } +} diff --git a/resources/views/theme/backend/adminlte/a/product/details.blade.php b/resources/views/theme/backend/adminlte/a/product/details.blade.php new file mode 100644 index 0000000..eee57a3 --- /dev/null +++ b/resources/views/theme/backend/adminlte/a/product/details.blade.php @@ -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') +
+
+ @include('adminlte::widget.status') +
+
+ +
+
+
+
+ +
+ +
+
+
+ @include('a.product.widgets.detail') +
+ + {{-- +
+ @include('a.product.widgets.products') +
+ +
+ @include('a.product.widgets.offerings') +
+ +
+ @include('a.product.widgets.connections') +
+ --}} +
+
+
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/a/product/home.blade.php b/resources/views/theme/backend/adminlte/a/product/home.blade.php new file mode 100644 index 0000000..9538acc --- /dev/null +++ b/resources/views/theme/backend/adminlte/a/product/home.blade.php @@ -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') +
+
+ +
+
+

Product Configuration

+
+ +
+
+ @csrf + +
+
+
+ + + + @error('name') + {{ $message }} + @else + Product Name is required. + @enderror + + Product Name +
+
+
+
+
+
+
+
+@endsection + +@section('page-scripts') + @css(select2) + @js(select2,autofocus) + + +@endsection \ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/a/product/widgets/detail.blade.php b/resources/views/theme/backend/adminlte/a/product/widgets/detail.blade.php new file mode 100644 index 0000000..d379322 --- /dev/null +++ b/resources/views/theme/backend/adminlte/a/product/widgets/detail.blade.php @@ -0,0 +1,155 @@ + +
+
+

Product Details

+
+ @if(session()->has('success')) + {{ session()->get('success') }} + @endif + +
+ @csrf + +
+
+
+ +
+
+ + + + @error('description.name') + {{ $message }} + @else + Product Name required. + @enderror + +
+
+ + +
+
+
+ active) ? 'checked' : '' }}> + +
+
+
+
+
+
+ +
+ +
+
+ + + + @error('model') + {{ $message }} + @else + Product Type required. + @enderror + +
+
+
+ +
+ +
+
+ + + + @error('model_id') + {{ $message }} + @else + Supplied Product required. + @enderror + +
+
+
+ +
+ +
+ Cancel + @can('wholesaler') + + @endcan +
+
+
+
+
+ +@section('page-scripts') + @css(select2) + @js(select2,autofocus) + + +@endsection \ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/a/service/widgets/domain/update.blade.php b/resources/views/theme/backend/adminlte/a/service/widgets/domain/update.blade.php index d97f8b1..90d5355 100644 --- a/resources/views/theme/backend/adminlte/a/service/widgets/domain/update.blade.php +++ b/resources/views/theme/backend/adminlte/a/service/widgets/domain/update.blade.php @@ -6,18 +6,18 @@
- +
.
- @foreach(\App\Models\DomainTld::active()->orderBy('name')->get() as $oo) - + @endforeach - @error('service.domain_name') + @error('domain.domain_name') {{ $message }} @else Domain Name is required. @@ -35,9 +35,9 @@
- + - @error('service.domain_expire') + @error('domain.domain_expire') {{ $message }} @else Domain Expiry is required. @@ -57,15 +57,15 @@
- @foreach(\App\Models\DomainRegistrar::active()->orderBy('name')->get() as $oo) - + @endforeach - @error('service.domain_registrar_id') + @error('domain.domain_registrar_id') {{ $message }} @else Domain Registrar is required. @@ -81,11 +81,11 @@
- +
- + - @error('service.registrar_account') + @error('domain.registrar_account') {{ $message }} @else Registrar Account ID is required. @@ -105,13 +105,13 @@
- @foreach(\App\Models\Service\Domain::select('registrar_ns')->distinct()->get() as $oo) - + @endforeach - @error('service.registrar_ns') + @error('domain.registrar_ns') {{ $message }} @else DNS Details is required. @@ -127,11 +127,11 @@
- +
- + - @error('service.registrar_username') + @error('domain.registrar_username') {{ $message }} @else Registrar Username is required. @@ -162,6 +162,13 @@ +@append \ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/a/service/widgets/internal.blade.php b/resources/views/theme/backend/adminlte/a/service/widgets/internal.blade.php index 05e1698..66df046 100644 --- a/resources/views/theme/backend/adminlte/a/service/widgets/internal.blade.php +++ b/resources/views/theme/backend/adminlte/a/service/widgets/internal.blade.php @@ -22,9 +22,9 @@ ${{ number_format($b=\App\Models\Tax::tax_calc($o->product->setup_charge,$o->account->taxes),2) }} @if ($a > $b) - ({{ number_format(($a-$b)/($b ?: 1)*100,1) }})% + ({{ number_format($a ? ($b-$a)/($b ?: 1)*100 : 100,1) }})% @else - {{ number_format(($b-$a)/($a ?: 1)*100,1) }}% + {{ number_format($a ? ($b-$a)/($b ?: 1)*100 : ($b ? 100: 0),1) }}% @endif @@ -40,9 +40,9 @@ ${{ number_format($b=\App\Models\Tax::tax_calc($o->product->getBaseChargeAttribute($o->billing_interval),$o->account->taxes),2) }} @if ($a > $b) - ({{ number_format(($a-$b)/($b ?: 1)*100,1) }})% + ({{ number_format($a ? ($b-$a)/($b ?: 1)*100 : 100,1) }})% @else - {{ number_format(($b-$a)/($a ?: 1)*100,1) }}% + {{ number_format($a ? ($b-$a)/($b ?: 1)*100 : ($b ? 100: 0),1) }}% @endif @@ -52,9 +52,9 @@ ${{ 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) }} @if ($a > $b) - ({{ number_format(($a-$b)/($b ?: 1)*100,1) }})% + ({{ number_format($a ? ($b-$a)/($b ?: 1)*100 : 100,1) }})% @else - {{ number_format(($b-$a)/($a ?: 1)*100,1) }}% + {{ number_format($a ? ($b-$a)/($b ?: 1)*100 : ($b ? 100: 0),1) }}% @endif @@ -70,9 +70,9 @@ ${{ number_format($b=\App\Models\Tax::tax_calc($o->product->getMinChargeAttribute($o->billing_interval),$o->account->taxes),2) }} @if ($a > $b) - ({{ number_format(($a-$b)/($b ?: 1)*100,1) }})% + ({{ number_format($a ? ($b-$a)/($b ?: 1)*100 : 100,1) }})% @else - {{ number_format(($b-$a)/($a ?: 1)*100,1) }}% + {{ number_format($a ? ($b-$a)/($b ?: 1)*100 : ($b ? 100: 0),1) }}% @endif diff --git a/resources/views/theme/backend/adminlte/a/service/widgets/update.blade.php b/resources/views/theme/backend/adminlte/a/service/widgets/update.blade.php index 26284ec..ffbbf8a 100644 --- a/resources/views/theme/backend/adminlte/a/service/widgets/update.blade.php +++ b/resources/views/theme/backend/adminlte/a/service/widgets/update.blade.php @@ -2,7 +2,7 @@

Update Service details

-
+ @csrf @includeIf('a.service.widgets.'.$o->stype.'.update',['o'=>$o->type]) diff --git a/resources/views/theme/backend/adminlte/a/service/widgets/voip/update.blade.php b/resources/views/theme/backend/adminlte/a/service/widgets/voip/update.blade.php index e883f0d..be6c89f 100644 --- a/resources/views/theme/backend/adminlte/a/service/widgets/voip/update.blade.php +++ b/resources/views/theme/backend/adminlte/a/service/widgets/voip/update.blade.php @@ -1,3 +1,4 @@ +
@@ -32,4 +33,40 @@
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
\ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/a/supplier/widgets/detail.blade.php b/resources/views/theme/backend/adminlte/a/supplier/widgets/detail.blade.php index 02e1f22..b41bf2c 100644 --- a/resources/views/theme/backend/adminlte/a/supplier/widgets/detail.blade.php +++ b/resources/views/theme/backend/adminlte/a/supplier/widgets/detail.blade.php @@ -28,7 +28,7 @@
- +
@@ -113,7 +113,7 @@
- + @error('supplier_details.accounts') {{ $message }} @@ -128,7 +128,7 @@
- + @error('supplier_details.support') {{ $message }} @@ -143,7 +143,7 @@
- + @error('supplier_details.payments') {{ $message }} diff --git a/resources/views/theme/backend/adminlte/r/service/domain/list.blade.php b/resources/views/theme/backend/adminlte/r/service/domain/list.blade.php index f41f993..64bb547 100644 --- a/resources/views/theme/backend/adminlte/r/service/domain/list.blade.php +++ b/resources/views/theme/backend/adminlte/r/service/domain/list.blade.php @@ -45,7 +45,7 @@ {{ $oo->registrar_ns }} @if ($oo->service->isBilled()) {{ $oo->service->invoice_next->format('Y-m-d') }} @else - @endif @if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items(TRUE)->sum('total'),2) }}@else - @endif - {{ $oo->service->billing_period }} + {{ $oo->service->billing_interval_string }} @endforeach diff --git a/resources/views/theme/backend/adminlte/r/service/email/list.blade.php b/resources/views/theme/backend/adminlte/r/service/email/list.blade.php new file mode 100644 index 0000000..ae22093 --- /dev/null +++ b/resources/views/theme/backend/adminlte/r/service/email/list.blade.php @@ -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') +
+
+
+
+ + + + + + + + + + + + + + + + + + @foreach ($o as $oo) + service_expire->isPast()) class="table-danger" @endif> + + + + + + + + + + + + @endforeach + +
Service IDAccountDomainExpiresSupplierAdmin URLAdmin DetailsNext BilledPriceTerm
{{ $oo->service->sid }}{{ $oo->service->account->name }}{{ $oo->service_name }}{{ $oo->service_expire->format('Y-m-d') }}{{ $oo->service->product->supplier->name }}{{ $oo->admin_url }}@if($oo->admin_user){{ $oo->admin_user }}/{{ $oo->admin_pass }}@else   @endif@if ($oo->service->isBilled()) {{ $oo->service->invoice_next->format('Y-m-d') }} @else - @endif@if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items(TRUE)->sum('total'),2) }}@else - @endif{{ $oo->service->billing_interval_string }}
+
+
+
+
+@endsection + +@section('page-scripts') + @css(datatables,bootstrap4|rowgroup) + @js(datatables,bootstrap4|rowgroup) + + + +@append \ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/u/service/home.blade.php b/resources/views/theme/backend/adminlte/u/service/home.blade.php index 06e101e..ea4b2ed 100644 --- a/resources/views/theme/backend/adminlte/u/service/home.blade.php +++ b/resources/views/theme/backend/adminlte/u/service/home.blade.php @@ -42,7 +42,7 @@ --}} @can('wholesaler') - + @endcan diff --git a/resources/views/theme/backend/adminlte/u/service/widgets/email/details.blade.php b/resources/views/theme/backend/adminlte/u/service/widgets/email/details.blade.php new file mode 100644 index 0000000..0f63036 --- /dev/null +++ b/resources/views/theme/backend/adminlte/u/service/widgets/email/details.blade.php @@ -0,0 +1,45 @@ + +{{-- @todo Change this to App\Models\Service\Email --}} +
+ @if($o->service->isPending()) +
+
+ Pending +
+
+ @endif + +
+

Email Hosting Details

+
+ +
+ + + + + + @if($o->service_connect_date) + + + + + @endif + @if ($o->inContract()) + + + + + + + + + + @endif + + + + +
Domain Name{{ $o->service_name }}
Connected{{ $o->service_connect_date->format('Y-m-d') }}
Contract12 months ({{ ($x=$o->domain_expire)->diffForHumans() }})
Contract End{{ $x->format('Y-m-d') }}
Cancel NoticeBefore renewal
+
+
\ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/u/service/widgets/information.blade.php b/resources/views/theme/backend/adminlte/u/service/widgets/information.blade.php index abfb36a..dbd6e36 100644 --- a/resources/views/theme/backend/adminlte/u/service/widgets/information.blade.php +++ b/resources/views/theme/backend/adminlte/u/service/widgets/information.blade.php @@ -15,7 +15,7 @@ - + diff --git a/resources/views/vendor/adminlte/layouts/partials/sidebarmenu.blade.php b/resources/views/vendor/adminlte/layouts/partials/sidebarmenu.blade.php index a2429ec..7dc56d8 100644 --- a/resources/views/vendor/adminlte/layouts/partials/sidebarmenu.blade.php +++ b/resources/views/vendor/adminlte/layouts/partials/sidebarmenu.blade.php @@ -77,6 +77,12 @@ + + - + + @endcan \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 3992a17..a4d0fbf 100644 --- a/routes/api.php +++ b/routes/api.php @@ -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']); diff --git a/routes/web.php b/routes/web.php index 6a4fc8f..27687b9 100644 --- a/routes/web.php +++ b/routes/web.php @@ -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
Account{{ $o->account->aid }}{{ $o->account->aid }}
Status