Enable editing of supplier products and listing services connected to them

This commit is contained in:
Deon George 2022-06-30 23:51:20 +10:00
parent fb416306e7
commit 5297ae8a62
33 changed files with 963 additions and 182 deletions

View File

@ -2,7 +2,8 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Requests\SupplierAddEdit; use Illuminate\Http\Request;
use App\Http\Requests\{SupplierAddEdit,SupplierProductAddEdit};
use App\Models\{Cost,Supplier,SupplierDetail}; use App\Models\{Cost,Supplier,SupplierDetail};
class SupplierController extends Controller class SupplierController extends Controller
@ -67,6 +68,111 @@ class SupplierController extends Controller
return view('supplier.cost',['o'=>$o]); return view('supplier.cost',['o'=>$o]);
} }
/**
* New Product from a supplier
*
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
*/
public function product_add()
{
return view('supplier.product.addedit')
->with('o',new Supplier)
->with('oo',NULL);
}
public function product_addedit(SupplierProductAddEdit $request,Supplier $o,int $id,string $type)
{
// Quick validation
if ($type !== $request->offering_type)
abort(500,'Type and offering type do not match');
if ($o->exists && ($o->detail->id !== (int)$request->supplier_detail_id))
abort(500,sprintf('Supplier [%d] and supplier_detail_id [%d] do not match',$o->detail->id,$request->supplier_detail_id));
switch ($request->offering_type) {
case 'broadband':
$oo = Supplier\Broadband::findOrNew($id);
// @todo these are broadband requirements - get them from the broadband class.
foreach ($request->only([
'supplier_detail_id',
'product_id'.
'product_desc',
'base_cost',
'setup_cost',
'contract_term',
'metric',
'speed',
'technology',
'offpeak_start',
'offpeak_end',
'base_down_peak',
'base_up_peak',
'base_down_offpeak',
'base_up_offpeak',
'extra_down_peak',
'extra_up_peak',
'extra_down_offpeak',
'extra_up_offpeak',
]) as $key => $value)
$oo->$key = $value;
// Our boolean values
foreach ($request->only(['active','extra_shaped','extra_charged']) as $key => $value)
$oo->$key = ($value == 'on' ? 1 : 0);
break;
default:
throw new \Exception('Unknown offering type:'.$request->offering_type);
}
$oo->save();
return redirect()->back()
->with('success','Saved');
}
/**
* Edit a supplier product
*
* @param Supplier $o
* @param int $id
* @param string $type
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
*/
public function product_view(Supplier $o,int $id,string $type)
{
$oo = $o->detail->find($type,$id);
$oo->load(['products.product.services.product.type']);
return view('supplier.product.addedit')
->with('o',$o)
->with('oo',$oo);
}
/**
* Return the form for a specific product type
*
* @param Request $request
* @param string $type
* @param int|null $id
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
*/
public function product_view_type(Request $request,string $type,int $id=NULL)
{
$o = $id ? Supplier::offeringTypeClass($type)->findOrFail($id) : NULL;
if ($request->old)
$request->session()->flashInput($request->old);
if ($o)
$o->load(['products.product.services']);
return view('supplier.product.widget.'.$type)
->with('o',$id ? $o : NULL)
->withErrors($request->errors);
}
/** /**
* View a supplier. * View a supplier.
* *

View File

@ -0,0 +1,60 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
use App\Models\Supplier;
class SupplierProductAddEdit extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Auth::user()->isWholesaler();
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules(Request $request)
{
// @todo these are broadband requirements - perhaps move them to the broadband class.
// @todo Enhance the validation so that extra_* values are not accepted if base_* values are not included.
return [
'id' => 'required|nullable',
'offering_type' => ['required',Rule::in(Supplier::offeringTypeKeys()->toArray())],
'supplier_detail_id' => 'required|exists:supplier_details,id',
'active' => 'sometimes|accepted',
'extra_shaped' => 'sometimes|accepted',
'extra_charged' => 'sometimes|accepted',
'product_id' => 'required|string|min:2',
'product_desc' => 'required|string|min:2',
'base_cost' => 'required|numeric|min:.01',
'setup_cost' => 'nullable|numeric',
'contract_term' => 'nullable|numeric|min:1',
'metric' => 'nullable|numeric|min:1',
'speed' => 'nullable|string|max:64',
'technology' => 'nullable|string|max:255',
'offpeak_start' => 'nullable|date_format:H:i',
'offpeak_end' => 'nullable|date_format:H:i',
'base_down_peak' => 'nullable|numeric',
'base_up_peak' => 'nullable|numeric',
'base_down_offpeak' => 'nullable|numeric',
'base_up_offpeak' => 'nullable|numeric',
'extra_down_peak' => 'nullable|numeric',
'extra_up_peak' => 'nullable|numeric',
'extra_down_offpeak' => 'nullable|numeric',
'extra_up_offpeak' => 'nullable|numeric',
];
}
}

View File

@ -16,9 +16,9 @@ interface SupplierItem
/** /**
* Available products created from this supplier offering * Available products created from this supplier offering
* *
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany * @return \Illuminate\Database\Eloquent\Relations\HasMany
*/ */
public function types(); public function products();
/* ATTRIBUTES */ /* ATTRIBUTES */

View File

@ -183,11 +183,10 @@ class Product extends Model implements IDs
* Return the type of service is provided. eg: Broadband, Phone. * Return the type of service is provided. eg: Broadband, Phone.
* *
* @return string * @return string
* @todo Does type need to be a mandatory attribute on a model - then we can remove this condition
*/ */
public function getCategoryAttribute(): string public function getCategoryAttribute(): string
{ {
return $this->type ? $this->type->getCategoryAttribute() : 'generic'; return $this->supplied->getCategoryAttribute();
} }
/** /**
@ -195,11 +194,10 @@ class Product extends Model implements IDs
* other logic of these types. * other logic of these types.
* *
* @return string * @return string
* @todo Does type need to be a mandatory attribute on a model - then we can remove this condition
*/ */
public function getCategoryNameAttribute(): string public function getCategoryNameAttribute(): string
{ {
return $this->type ? $this->type->getCategoryNameAttribute() : 'Generic'; return $this->supplied->getCategoryNameAttribute();
} }
/** /**

View File

@ -15,8 +15,6 @@ final class Broadband extends Type implements ProductItem
protected $table = 'product_broadband'; protected $table = 'product_broadband';
protected const category_name = 'Broadband';
// Information required during the order process // Information required during the order process
protected array $order_attributes = [ protected array $order_attributes = [
'options.address'=>[ 'options.address'=>[

View File

@ -12,8 +12,6 @@ final class Domain extends Type implements ProductItem
{ {
protected $table = 'product_domain'; protected $table = 'product_domain';
protected const category_name = 'Domain Name';
// The model that is referenced when this product is ordered // The model that is referenced when this product is ordered
protected string $order_model = ServiceDomain::class; protected string $order_model = ServiceDomain::class;

View File

@ -12,8 +12,6 @@ final class Email extends Type implements ProductItem
{ {
protected $table = 'product_email'; protected $table = 'product_email';
protected const category_name = 'Email Hosting';
// The model that is referenced when this product is ordered // The model that is referenced when this product is ordered
protected string $order_model = ServiceEmail::class; protected string $order_model = ServiceEmail::class;

View File

@ -12,8 +12,6 @@ final class Generic extends Type implements ProductItem
{ {
protected $table = 'product_generic'; protected $table = 'product_generic';
protected const category_name = 'Generic';
// The model that is referenced when this product is ordered // The model that is referenced when this product is ordered
protected string $order_model = ServiceGeneric::class; protected string $order_model = ServiceGeneric::class;

View File

@ -12,8 +12,6 @@ final class Host extends Type implements ProductItem
{ {
protected $table = 'product_host'; protected $table = 'product_host';
protected const category_name = 'Web Hosting';
// The model that is referenced when this product is ordered // The model that is referenced when this product is ordered
protected string $order_model = ServiceHost::class; protected string $order_model = ServiceHost::class;

View File

@ -12,8 +12,6 @@ final class Phone extends Type implements ProductItem
{ {
protected $table = 'product_phone'; protected $table = 'product_phone';
protected const category_name = 'Telephone';
protected array $order_attributes = [ protected array $order_attributes = [
'options.phonenumber'=>[ 'options.phonenumber'=>[
'request'=>'options.phonenumber', 'request'=>'options.phonenumber',

View File

@ -12,8 +12,6 @@ final class SSL extends Type implements ProductItem
{ {
protected $table = 'product_ssl'; protected $table = 'product_ssl';
protected const category_name = 'SSL Certificate';
// The model that is referenced when this product is ordered // The model that is referenced when this product is ordered
protected string $order_model = ServiceSSL::class; protected string $order_model = ServiceSSL::class;

View File

@ -32,9 +32,11 @@ abstract class Type extends Model
* other logic of these types. * other logic of these types.
* *
* @return string * @return string
* @deprecated - can this be replaced with product->supplied->category?
*/ */
final public function getCategoryAttribute(): string final public function getCategoryAttribute(): string
{ {
abort(500,'use product->supplied->category_name');
return strtolower((new \ReflectionClass($this))->getShortName()); return strtolower((new \ReflectionClass($this))->getShortName());
} }
@ -42,9 +44,11 @@ abstract class Type extends Model
* Return a friendly name for this product, used for display * Return a friendly name for this product, used for display
* *
* @return string * @return string
* @deprecated - can this be replaced with product->supplied->category_name
*/ */
final public function getCategoryNameAttribute(): string final public function getCategoryNameAttribute(): string
{ {
abort(500,'use product->supplied->category_name');
return static::category_name; return static::category_name;
} }
} }

View File

@ -8,57 +8,93 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Leenooks\Traits\ScopeActive; use Leenooks\Traits\ScopeActive;
use App\Models\Supplier\{Broadband,Domain,Email,Ethernet,Generic,Host,HSPA,Phone,SSL}; use App\Models\Supplier\{Broadband,Domain,Email,Ethernet,Generic,Host,HSPA,Phone,SSL,Type};
class Supplier extends Model class Supplier extends Model
{ {
/**
* The offerings types we provide
*/
private const offering_types = [
'broadband' => Broadband::class,
'hspa' => HSPA::class,
'ethernet' => Ethernet::class,
'domainname' => Domain::class,
'email' => Email::class,
'generic' => Generic::class,
'hosting' => Host::class,
'phone' => Phone::class,
'ssl' => SSL::class,
];
use ScopeActive; use ScopeActive;
public $timestamps = FALSE; public $timestamps = FALSE;
/* STATIC METHODS */
/** /**
* The offerings we provide * Return the offerings that this supplier provides
* @todo Use the product/* category instead of this const. The assumption is the supplier/* type is the same as the product/* type. *
* @deprecated - use the product/* category instead. * @param Supplier|null $so
* @return Collection
*/ */
public const offering_types = [ public static function offeringTypes(self $so=NULL): Collection
'broadband' => [ {
'name' => 'Broadband', $result = collect();
'class' => Broadband::class,
], foreach (self::offering_types as $type) {
'hspa' => [ $class = new $type;
'name' => 'Mobile Broadband',
'class' => HSPA::class, if ($so) {
], // If we have a connections configuration for that supplier, then build the child relationships
'ethernet' => [ if (Arr::get($so->detail->connections,$class->category)) {
'name' => 'Ethernet Broadband', $result->put($class->category,(object)[
'class' => Ethernet::class, 'type' => $class->category_name,
], 'items' => $class->where('supplier_detail_id',$so->detail->id),
'domainname' => [ ]);
'name' => 'Domain Name',
'class' => Domain::class, continue;
], }
'email' => [
'name' => 'Email Hosting', // Even if we dont have any connections, see if we have any products defined
'class' => Email::class, $o = new $class;
], $o->where('supplier_detail_id',$so->detail->id);
'generic' => [
'name' => 'Generic', if ($o->count())
'class' => Generic::class, $result->put($class->category,(object)[
], 'type' => $class->category_name,
'hosting' => [ 'items' => $class->where('supplier_detail_id',$so->detail->id),
'name' => 'Hosting', ]);
'class' => Host::class,
], } else {
'phone' => [ $result->put($class->category_name,$class);
'name' => 'Phone', }
'class' => Phone::class, }
],
'ssl' => [ return $result;
'name' => 'SSL', }
'class' => SSL::class,
], /**
]; * Return a new model object for the offering type
*
* @param string $type
* @return Type
*/
public static function offeringTypeClass(string $type): Type
{
return ($class=collect(self::offering_types)->get($type)) ? new $class : new Generic;
}
/**
* Return our supported offering type keys
*
* @return Collection
*/
public static function offeringTypeKeys(): Collection
{
return collect(self::offering_types)->keys();
}
/* RELATIONS */ /* RELATIONS */
@ -73,43 +109,6 @@ class Supplier extends Model
/* METHODS */ /* METHODS */
/**
* Return the offerings that this supplier provides
*
* @return void
*/
public function offeringTypes(): Collection
{
$result = collect();
// See if we have any configurations
foreach (self::offering_types as $key => $type) {
if (! ($class=Arr::get($type,'class')))
continue;
if (Arr::get($this->detail->connections,$key)) {
$result->put($key,(object)[
'type' => Arr::get($type,'name'),
'items' => (new $class)->where('supplier_detail_id',$this->detail->id),
]);
continue;
}
// See if we have any products defined
$o = new $class;
$o->where('supplier_detail_id',$this->detail->id);
if ($o->count())
$result->put($key,(object)[
'type' => Arr::get($type,'name'),
'items' => (new $class)->where('supplier_detail_id',$this->detail->id),
]);
}
return $result;
}
/** /**
* Return the traffic records, that were not matched to a service. * Return the traffic records, that were not matched to a service.
* *

View File

@ -10,6 +10,8 @@ use App\Models\Product\Broadband as ProductBroadband;
class Broadband extends Type implements SupplierItem class Broadband extends Type implements SupplierItem
{ {
protected const category_name = 'Broadband';
protected $casts = [ protected $casts = [
'offpeak_start' => 'datetime:H:i', 'offpeak_start' => 'datetime:H:i',
'offpeak_end' => 'datetime:H:i', 'offpeak_end' => 'datetime:H:i',
@ -35,16 +37,16 @@ class Broadband extends Type implements SupplierItem
/* INTERFACES */ /* INTERFACES */
public function types()
{
return $this->belongsToMany(ProductBroadband::class,$this->table,'id','id','id','supplier_item_id');
}
public function getBillingIntervalAttribute(): int public function getBillingIntervalAttribute(): int
{ {
return 1; // Monthly return 1; // Monthly
} }
public function products()
{
return $this->hasMany(ProductBroadband::class,'supplier_item_id','id');
}
/* METHODS */ /* METHODS */
/** /**
@ -156,14 +158,4 @@ class Broadband extends Type implements SupplierItem
return $result; return $result;
} }
/**
* Return the Broadband Speed
*
* @return string
*/
public function speed(): string
{
return $this->speed;
}
} }

View File

@ -8,6 +8,8 @@ use App\Models\TLD;
final class Domain extends Type implements SupplierItem final class Domain extends Type implements SupplierItem
{ {
protected const category_name = 'Domain Name';
protected $table = 'supplier_domain'; protected $table = 'supplier_domain';
/* INTERFACES */ /* INTERFACES */
@ -22,9 +24,9 @@ final class Domain extends Type implements SupplierItem
return sprintf('%s: %s',$this->product_id,$this->tld->name); return sprintf('%s: %s',$this->product_id,$this->tld->name);
} }
public function types() public function products()
{ {
return $this->belongsToMany(ProductDomain::class,$this->table,'id','id','id','supplier_item_id'); return $this->hasMany(ProductDomain::class,'supplier_item_id','id');
} }
/* RELATIONS */ /* RELATIONS */

View File

@ -7,6 +7,8 @@ use App\Models\Product\Email as ProductEmail;
final class Email extends Type implements SupplierItem final class Email extends Type implements SupplierItem
{ {
protected const category_name = 'Email Hosting';
protected $table = 'supplier_email'; protected $table = 'supplier_email';
/* INTERFACES */ /* INTERFACES */
@ -16,8 +18,8 @@ final class Email extends Type implements SupplierItem
return 4; // Yearly return 4; // Yearly
} }
public function types() public function products()
{ {
return $this->belongsToMany(ProductEmail::class,$this->table,'id','id','id','supplier_item_id'); return $this->hasMany(ProductEmail::class,$this->table,'supplier_item_id','id');
} }
} }

View File

@ -4,4 +4,5 @@ namespace App\Models\Supplier;
class Ethernet extends Broadband class Ethernet extends Broadband
{ {
protected const category_name = 'Broadband Ethernet';
} }

View File

@ -7,17 +7,19 @@ use App\Models\Product\Generic as ProductGeneric;
final class Generic extends Type implements SupplierItem final class Generic extends Type implements SupplierItem
{ {
protected const category_name = 'Generic';
protected $table = 'supplier_generic'; protected $table = 'supplier_generic';
/* INTERFACES */ /* INTERFACES */
public function types()
{
return $this->belongsToMany(ProductGeneric::class,$this->table,'id','id','id','supplier_item_id');
}
public function getBillingIntervalAttribute(): int public function getBillingIntervalAttribute(): int
{ {
return 1; // Monthly return 1; // Monthly
} }
public function products()
{
return $this->hasMany(ProductGeneric::class,'supplier_item_id','id');
}
} }

View File

@ -4,4 +4,5 @@ namespace App\Models\Supplier;
class HSPA extends Broadband class HSPA extends Broadband
{ {
protected const category_name = 'Mobile Broadband';
} }

View File

@ -7,17 +7,19 @@ use App\Models\Product\Host as ProductHost;
final class Host extends Type implements SupplierItem final class Host extends Type implements SupplierItem
{ {
protected const category_name = 'Web Hosting';
protected $table = 'supplier_host'; protected $table = 'supplier_host';
/* INTERFACES */ /* INTERFACES */
public function types()
{
return $this->belongsToMany(ProductHost::class,$this->table,'id','id','id','supplier_item_id');
}
public function getBillingIntervalAttribute(): int public function getBillingIntervalAttribute(): int
{ {
return 4; // Yearly return 4; // Yearly
} }
public function products()
{
return $this->belongsToMany(ProductHost::class,'supplier_item_id','id');
}
} }

View File

@ -7,17 +7,19 @@ use App\Models\Product\Phone as ProductVoip;
final class Phone extends Type implements SupplierItem final class Phone extends Type implements SupplierItem
{ {
protected const category_name = 'Telephone';
protected $table = 'supplier_phone'; protected $table = 'supplier_phone';
/* INTERFACES */ /* INTERFACES */
public function types()
{
return $this->belongsToMany(ProductVoip::class,$this->table,'id','id','id','supplier_item_id');
}
public function getBillingIntervalAttribute(): int public function getBillingIntervalAttribute(): int
{ {
return 1; // Monthly return 1; // Monthly
} }
public function products()
{
return $this->hasMany(ProductVoip::class,'supplier_item_id','id');
}
} }

View File

@ -7,17 +7,19 @@ use App\Models\Product\SSL as ProductSSL;
final class SSL extends Type implements SupplierItem final class SSL extends Type implements SupplierItem
{ {
protected const category_name = 'SSL Certificate';
protected $table = 'supplier_ssl'; protected $table = 'supplier_ssl';
/* INTERFACES */ /* INTERFACES */
public function types()
{
return $this->belongsToMany(ProductSSL::class,$this->table,'id','id','id','supplier_item_id');
}
public function getBillingIntervalAttribute(): int public function getBillingIntervalAttribute(): int
{ {
return 4; // Yearly return 4; // Yearly
} }
public function products()
{
return $this->belongsToMany(ProductSSL::class,'supplier_item_id','id');
}
} }

View File

@ -27,6 +27,27 @@ abstract class Type extends Model
return Tax::tax_calc($this->attributes['base_cost'],config('site')->taxes); return Tax::tax_calc($this->attributes['base_cost'],config('site')->taxes);
} }
/**
* This will return the category of the product (eg: domain, hosting, etc) which is the basis for all
* other logic of these types.
*
* @return string
*/
final public function getCategoryAttribute(): string
{
return strtolower((new \ReflectionClass($this))->getShortName());
}
/**
* Return a friendly name for this product, used for display
*
* @return string
*/
final public function getCategoryNameAttribute(): string
{
return static::category_name;
}
/** /**
* This contract term is the highest of * This contract term is the highest of
* + The defined contract_term * + The defined contract_term

View File

@ -3,7 +3,9 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Models\Supplier\{Broadband,Generic,Phone,Type};
use App\Traits\SiteID; use App\Traits\SiteID;
class SupplierDetail extends Model class SupplierDetail extends Model
@ -14,8 +16,50 @@ class SupplierDetail extends Model
/* RELATIONS */ /* RELATIONS */
public function broadbands()
{
return $this->hasMany(Broadband::class);
}
public function generics()
{
return $this->hasMany(Generic::class);
}
public function phones()
{
return $this->hasMany(Phone::class);
}
public function supplier() public function supplier()
{ {
return $this->belongsTo(Supplier::class); return $this->belongsTo(Supplier::class);
} }
/* METHODS */
/**
* Find a supplier product of a particular type
*
* @param string $type
* @param int $id
* @return Type
* @throws \Exception
*/
public function find(string $type,int $id): Type
{
switch ($type) {
case 'broadband':
$item = $this->broadbands->where('id',$id);
break;
default:
throw new \Exception('Unknown type: '.$type);
}
if ($item->count() !== 1)
throw new ModelNotFoundException(sprintf('Unknown Model of type [%s] with id [%d]',$type,$id));
return $item->pop();
}
} }

View File

@ -0,0 +1,219 @@
<!-- $o = Supplier::class -->
<!-- $oo = Supplier/{type}::class -->
@extends('adminlte::layouts.app')
@section('htmlheader_title')
Supplier - @if ($oo && $oo->exists)Edit @else New @endif Product
@endsection
@section('page_title')
@if ($oo && $oo->exists)Edit @else New @endif Product
@endsection
@section('contentheader_title')
Supplier - @if ($oo && $oo->exists)Edit @else New @endif Product
@endsection
@section('contentheader_description')
@endsection
@section('main-content')
<div class="row">
<div class="col-8">
<div class="card card-dark">
<div class="card-header">
<h1 class="card-title">Supplier Product</h1>
@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
</div>
<div class="card-body">
<form class="g-0 needs-validation" method="POST" enctype="multipart/form-data" role="form">
@csrf
<input type="hidden" name="id" value="{{ $oo?->id }}">
<div class="row">
<div class="col-4">
<!-- Supplier -->
<div class="form-group has-validation">
<label for="supplier_detail_id">Supplier</label>
<select class="form-control form-control-border @error('supplier_detail_id') is-invalid @enderror" id="supplier_detail_id" name="supplier_detail_id">
<option value=""></option>
@foreach(\App\Models\Supplier::active()->orderBy('name')->get() as $so)
<option value="{{ $so->id }}" {{ old('supplier_detail_id',$o->id) == $so->detail->id ? 'selected' : ''}}>{{ $so->name }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('supplier_detail_id')
{{ $message }}
@else
Date is required.
@enderror
</span>
<span class="input-helper">Suppliers Name</span>
</div>
</div>
<div class="col-4">
<!-- Offering Type -->
<div class="form-group has-validation">
<label for="offering_type">Type</label>
<select class="form-control form-control-border @error('offering_type') is-invalid @enderror" id="offering_type" name="offering_type">
<option value=""></option>
@foreach(\App\Models\Supplier::offeringTypes()->sortBy(function($item) { return $item->category_name; }) as $to)
<option value="{{ $to->category }}" {{ old('offering_type',$oo?->category) == $to->category ? 'selected' : ''}}>{{ $to->category_name }}</option>
@endforeach
</select>
<span class="invalid-feedback" role="alert">
@error('offering_type')
{{ $message }}
@else
Offering Type is Required
@enderror
</span>
<span class="input-helper">Offering Type</span>
</div>
</div>
</div>
<div id="type"></div>
<div class="row">
<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 ($oo && $oo->exists)Save @else Add @endif</button>
@endcan
</div>
</div>
</form>
</div>
</div>
</div>
@if($oo && $oo->exists)
<div class="col-4">
<div class="row">
<div class="col-12">
<div class="card card-info">
<div class="card-header">
<h1 class="card-title">Offering Products</h1>
</div>
<div class="card-body">
<table class="table table-sm">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th class="text-right">Services</th>
<th class="text-right">Sold</th>
</tr>
</thead>
<tbody>
@foreach ($oo->products as $pto)
<tr>
<td>{{ $pto->id }}</td>
<td>{{ $pto->product->name }}</td>
<td class="text-right">{{ $pto->product->services->where('active',true)->count() }}</td>
<td class="text-right">{{ $pto->product->services->count() }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card card-info">
<div class="card-header">
<h1 class="card-title">Services Using this Supplier Product</h1>
</div>
<div class="card-body">
<table class="table table-sm" id="services">
<thead>
<tr>
<th>ID</th>
<th>Product</th>
<th>Name</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@foreach ($oo->products as $pto)
@foreach ($pto->product->services as $so)
<tr>
<td><a href="{{ url('u/service',[$so->id]) }}">{{ $so->lid }}</a></td>
<td>{{ $so->product->type->id }}</td>
<td>{{ $so->name }}</td>
<td>{{ $so->active ? 'Active' : 'Not Active' }}</td>
</tr>
@endforeach
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@endif
</div>
@endsection
@section('page-scripts')
@css(datatables,bootstrap4|rowgroup)
@js(datatables,bootstrap4|responsive|rowgroup)
<script type="text/javascript">
$(document).ready(function() {
function load_type(type,id) {
$.ajax({
type: 'POST',
dataType: 'html',
cache: false,
data: {errors: {!! $errors !!}, old: {!! json_encode(old()) !!} },
url: '{{ url('a/supplier/product/view') }}/'+type+(id ? '/'+id : ''),
timeout: 2000,
error: function(x) {
spinner.toggleClass('d-none').toggleClass('fa-spin');
alert('Failed to submit');
},
success: function(data) {
$("div[id=type]").empty().append(data);
}
});
}
@if ($oo && $oo->exists)
$('#offering_type').attr('style','pointer-events: none;');
load_type('{{$oo->category}}',{{$oo->id}})
@endif
$('#offering_type').on('change',function() {
if (! $(this).val())
return;
load_type($(this).val());
});
$('#services').DataTable({
order: [[3,'asc'],[1,'asc'],[2,'asc']],
rowGroup: {
dataSrc: 3,
},
columnDefs: [
{
targets: [3],
visible: false,
}
],
});
});
</script>
@endsection

View File

@ -0,0 +1,308 @@
<!-- $o = Supplier{type}::class -->
<div class="row">
<!-- Supplier Name -->
<div class="col-3">
<div class="form-group has-validation">
<label for="product_id">Product ID</label>
<input type="text" class="form-control form-control-border @error('product_id') is-invalid @enderror" id="product_id" name="product_id" placeholder="Product ID" value="{{ old('product_id',$o?->product_id) }}" required>
<span class="invalid-feedback" role="alert">
@error('product_id')
{{ $message }}
@else
Supplier's Product ID
@enderror
</span>
<span class="input-helper">Supplier's Product ID</span>
</div>
</div>
<!-- Suppliers Description -->
<div class="offset-1 col-8">
<div class="form-group has-validation">
<label for="product_desc">Product Description</label>
<input type="text" class="form-control form-control-border @error('product_desc') is-invalid @enderror" id="product_desc" name="product_desc" placeholder="Product Description" value="{{ old('product_desc',$o?->product_desc) }}" required>
<span class="invalid-feedback" role="alert">
@error('product_desc')
{{ $message }}
@else
Product Description is required
@enderror
</span>
<span class="input-helper">Supplier's Product Description as it appears on Invoices</span>
</div>
</div>
</div>
<div class="row">
<!-- Supplier Active -->
<div class="col-1">
<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>
<!-- Base Cost -->
<div class="offset-1 col-2">
<div class="form-group has-validation">
<label for="base_cost">Base (ex)</label>
<input type="number" class="form-control form-control-border @error('base_cost') is-invalid @enderror" id="base_cost" name="base_cost" placeholder="Cost" value="{{ old('base_cost',$o?->base_cost) }}">
<span class="invalid-feedback" role="alert">
@error('base_cost')
{{ $message }}
@enderror
</span>
<span class="input-helper">Monthly Cost</span>
</div>
</div>
<!-- Setup Cost -->
<div class="col-2">
<div class="form-group has-validation">
<label for="setup_cost">Setup (ex)</label>
<input type="number" class="form-control form-control-border @error('setup_cost') is-invalid @enderror" id="setup_cost" name="setup_cost" placeholder="Setup" value="{{ old('setup_cost',$o?->setup_cost) }}">
<span class="invalid-feedback" role="alert">
@error('setup_cost')
{{ $message }}
@enderror
</span>
<span class="input-helper">Setup Cost</span>
</div>
</div>
<!-- Contract Term -->
<div class="col-2">
<div class="form-group has-validation">
<label for="contract_term">Contract Term</label>
<input type="number" class="form-control form-control-border @error('contract_term') is-invalid @enderror" id="contract_term" name="contract_term" placeholder="Term" value="{{ old('contract_term',$o?->contract_term) }}">
<span class="invalid-feedback" role="alert">
@error('contract_term')
{{ $message }}
@enderror
</span>
<span class="input-helper">Term (mths)</span>
</div>
</div>
<!-- Speed -->
<div class="col-2">
<div class="form-group has-validation">
<label for="speed">Speed</label>
<input type="text" class="form-control form-control-border @error('speed') is-invalid @enderror" id="speed" name="speed" placeholder="Speed" value="{{ old('speed',$o?->speed) }}">
<span class="invalid-feedback" role="alert">
@error('speed')
{{ $message }}
@enderror
</span>
<span class="input-helper">Speed</span>
</div>
</div>
<!-- Technology -->
<div class="col-2">
<div class="form-group has-validation">
<label for="technology">Technology</label>
<input type="text" class="form-control form-control-border @error('technology') is-invalid @enderror" id="technology" name="technology" placeholder="Speed" value="{{ old('technology',$o?->technology) }}">
<span class="invalid-feedback" role="alert">
@error('technology')
{{ $message }}
@enderror
</span>
<span class="input-helper">Technology</span>
</div>
</div>
</div>
<div class="row">
<!-- @todo Add javascript so these toggles cannot both be on -->
<!-- Extra Charged -->
<div class="col-1">
<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="extra_charged" name="extra_charged" {{ old('extra_charged',$o?->extra_charged) ? 'checked' : '' }}>
<label class="custom-control-label" for="extra_charged">Extra Charge</label>
</div>
</div>
</div>
<div class="offset-1 col-3">
<div class="form-group has-validation">
<label for="offpeak_start">OffPeak Start</label>
<input type="time" class="form-control form-control-border @error('offpeak_start') is-invalid @enderror" id="offpeak_start" name="offpeak_start" value="{{ old('offpeak_start',($o && $o->offpeak_start) ? $o->offpeak_start->format('H:i') : '') }}">
<span class="invalid-feedback" role="alert">
@error('offpeak_start')
{{ $message }}
@enderror
</span>
<span class="input-helper">Offpeak Start</span>
</div>
</div>
<div class="col-3">
<div class="form-group has-validation">
<label for="offpeak_end">OffPeak End</label>
<input type="time" class="form-control form-control-border @error('offpeak_end') is-invalid @enderror" id="offpeak_end" name="offpeak_end" value="{{ old('offpeak_end',($o && $o->offpeak_end) ? $o->offpeak_end->format('H:i') : '') }}">
<span class="invalid-feedback" role="alert">
@error('offpeak_end')
{{ $message }}
@enderror
</span>
<span class="input-helper">Offpeak Ends</span>
</div>
</div>
</div>
<div class="row mt-3">
<!-- Extra Shaped -->
<div class="col-1">
<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="extra_shaped" name="extra_shaped" {{ old('extra_shaped',$o?->extra_shaped) ? 'checked' : '' }}>
<label class="custom-control-label" for="extra_shaped">Extra Shaped</label>
</div>
</div>
</div>
<div class="offset-1 col-8">
<h3 class="text-md">Included Traffic in MB</h3>
</div>
</div>
<div class="row">
<!-- Base Down Peak -->
<div class="offset-2 col-2">
<div class="form-group has-validation">
<label for="base_down_peak">Down Peak</label>
<input type="number" class="form-control form-control-border @error('base_down_peak') is-invalid @enderror" id="base_down_peak" name="base_down_peak" value="{{ old('base_down_peak',$o?->base_down_peak) }}">
<span class="invalid-feedback" role="alert">
@error('base_down_peak')
{{ $message }}
@enderror
</span>
</div>
</div>
<!-- Base Down Peak -->
<div class="col-2">
<div class="form-group has-validation">
<label for="base_up_peak">Up Peak</label>
<input type="number" class="form-control form-control-border @error('base_up_peak') is-invalid @enderror" id="base_up_peak" name="base_up_peak" value="{{ old('base_up_peak',$o?->base_up_peak) }}">
<span class="invalid-feedback" role="alert">
@error('base_up_peak')
{{ $message }}
@enderror
</span>
</div>
</div>
<!-- Base Down Offpeak -->
<div class="col-2">
<div class="form-group has-validation">
<label for="base_down_offpeak">Down Offpeak</label>
<input type="number" class="form-control form-control-border @error('base_down_offpeak') is-invalid @enderror" id="base_down_offpeak" name="base_down_offpeak" value="{{ old('base_down_offpeak',$o?->base_down_offpeak) }}">
<span class="invalid-feedback" role="alert">
@error('base_down_offpeak')
{{ $message }}
@enderror
</span>
</div>
</div>
<!-- Base Down Offpeak -->
<div class="col-2">
<div class="form-group has-validation">
<label for="base_up_offpeak">Up Offpeak</label>
<input type="number" class="form-control form-control-border @error('base_up_offpeak') is-invalid @enderror" id="base_up_offpeak" name="base_up_offpeak" value="{{ old('base_up_offpeak',$o?->base_up_offpeak) }}">
<span class="invalid-feedback" role="alert">
@error('base_up_offpeak')
{{ $message }}
@enderror
</span>
</div>
</div>
<!-- Metric Divider -->
<div class="col-2">
<div class="form-group has-validation">
<label for="metric">Metric Divider</label>
<input type="number" class="form-control form-control-border @error('metric') is-invalid @enderror" id="metric" name="metric" placeholder="1000" value="{{ old('metric',$o?->metric) }}">
<span class="invalid-feedback" role="alert">
@error('metric')
{{ $message }}
@enderror
</span>
<span class="input-helper">Metric</span>
</div>
</div>
</div>
<div class="row mt-3">
<div class="offset-2 col-8">
<p class="text-small">Values determine the included traffic. A value of <strong>0</strong> means no traffic is included and charged at the Extra Traffic rates. An empty value means the traffic is not counted in this category.</p>
<p class="text-small">Any values charged at extra rates, the rate determines the value charged, and if the value is empty it will roll up to the <strong>Peak</strong> category or <strong>Down Peak</strong> if the Peak category is null.</p>
</div>
</div>
<hr>
<div class="row mt-3">
<div class="offset-2 col-8">
<h3 class="text-md">Extra Traffic in $/mb</h3>
</div>
</div>
<div class="row">
<!-- Extra Down Peak -->
<div class="offset-2 col-2">
<div class="form-group has-validation">
<label for="extra_down_peak">Down Peak</label>
<input type="number" class="form-control form-control-border @error('extra_down_peak') is-invalid @enderror" id="extra_down_peak" name="extra_down_peak" value="{{ old('extra_down_peak',$o?->extra_down_peak) }}">
<span class="invalid-feedback" role="alert">
@error('extra_down_peak')
{{ $message }}
@enderror
</span>
</div>
</div>
<!-- Extra Down Peak -->
<div class="col-2">
<div class="form-group has-validation">
<label for="extra_up_peak">Up Peak</label>
<input type="number" class="form-control form-control-border @error('extra_up_peak') is-invalid @enderror" id="extra_up_peak" name="extra_up_peak" value="{{ old('extra_up_peak',$o?->extra_up_peak) }}">
<span class="invalid-feedback" role="alert">
@error('extra_up_peak')
{{ $message }}
@enderror
</span>
</div>
</div>
<!-- Extra Down Offpeak -->
<div class="col-2">
<div class="form-group has-validation">
<label for="extra_down_offpeak">Down Offpeak</label>
<input type="number" class="form-control form-control-border @error('extra_down_offpeak') is-invalid @enderror" id="extra_down_offpeak" name="extra_down_offpeak" value="{{ old('extra_down_offpeak',$o?->extra_down_offpeak) }}">
<span class="invalid-feedback" role="alert">
@error('extra_down_offpeak')
{{ $message }}
@enderror
</span>
</div>
</div>
<!-- Extra Down Offpeak -->
<div class="col-2">
<div class="form-group has-validation">
<label for="extra_up_offpeak">Up Offpeak</label>
<input type="number" class="form-control form-control-border @error('extra_up_offpeak') is-invalid @enderror" id="extra_up_offpeak" name="extra_up_offpeak" value="{{ old('extra_up_offpeak',$o?->extra_up_offpeak) }}">
<span class="invalid-feedback" role="alert">
@error('extra_up_offpeak')
{{ $message }}
@enderror
</span>
</div>
</div>
</div>

View File

@ -14,7 +14,7 @@
</thead> </thead>
<tbody> <tbody>
@foreach ($o->offeringTypes() as $key => $offering) @foreach (\App\Models\Supplier::offeringTypes($o) as $key => $offering)
<tr> <tr>
<th>{{ $offering->type }}</th> <th>{{ $offering->type }}</th>
@if(Arr::get($o->detail->connections,$key)) @if(Arr::get($o->detail->connections,$key))

View File

@ -6,7 +6,7 @@
<select class="form-control form-control-border" id="cost_id" name="cost_id"> <select class="form-control form-control-border" id="cost_id" name="cost_id">
<option value=""></option> <option value=""></option>
<option value="">Add New</option> <option value="">Add New</option>
@foreach(\App\Models\Cost::orderBy('billed_at','DESC')->get() as $oo) @foreach(\App\Models\Cost::where('supplier_id',$o->id)->orderBy('billed_at','DESC')->get() as $oo)
<option value="{{ $oo->id }}">{{ $oo->billed_at->format('Y-m-d') }}: ${{ number_format($oo->total,2) }}</option> <option value="{{ $oo->id }}">{{ $oo->billed_at->format('Y-m-d') }}: ${{ number_format($oo->total,2) }}</option>
@endforeach @endforeach
</select> </select>

View File

@ -2,7 +2,7 @@
<div class="row"> <div class="row">
<div class="col-5 col-sm-2"> <div class="col-5 col-sm-2">
<div class="nav flex-column nav-tabs h-100" role="tablist" aria-orientation="vertical"> <div class="nav flex-column nav-tabs h-100" role="tablist" aria-orientation="vertical">
@foreach($o->offeringTypes() as $key => $offering) @foreach(\App\Models\Supplier::offeringTypes($o) as $key => $offering)
<a class="nav-link @if($loop->first)active @endif" id="offering-{{ $key }}-tab" data-toggle="pill" href="#offering-{{ $key }}-profile" role="tab" aria-controls="offering-{{ $key }}-tab" aria-selected="true">{{ $offering->type }}</a> <a class="nav-link @if($loop->first)active @endif" id="offering-{{ $key }}-tab" data-toggle="pill" href="#offering-{{ $key }}-profile" role="tab" aria-controls="offering-{{ $key }}-tab" aria-selected="true">{{ $offering->type }}</a>
@endforeach @endforeach
</div> </div>
@ -10,7 +10,7 @@
<div class="col-7 col-sm-10"> <div class="col-7 col-sm-10">
<div class="tab-content"> <div class="tab-content">
@foreach($o->offeringTypes() as $key => $offering) @foreach(\App\Models\Supplier::offeringTypes($o) as $key => $offering)
<div class="tab-pane text-left fade show @if($loop->first)active @endif" id="offering-{{ $key }}-profile" role="tabpanel" aria-labelledby="offering-{{ $key }}-tab"> <div class="tab-pane text-left fade show @if($loop->first)active @endif" id="offering-{{ $key }}-profile" role="tabpanel" aria-labelledby="offering-{{ $key }}-tab">
<table class="table table-sm table-bordered w-100" id="offering-{{ $key }}-table"> <table class="table table-sm table-bordered w-100" id="offering-{{ $key }}-table">
<thead> <thead>
@ -33,7 +33,7 @@
</thead> </thead>
<tbody> <tbody>
@foreach($xx=$offering->items->with(['types.product.services'])->get() as $oo) @foreach($xx=$offering->items->with(['products.product.services'])->get() as $oo)
<tr> <tr>
<td>{{ $oo->id }}</td> <td>{{ $oo->id }}</td>
<td>{{ $oo->name }}</td> <td>{{ $oo->name }}</td>
@ -41,9 +41,9 @@
<td class="text-right">{{ $oo->active ? 'YES' : 'NO' }}</td> <td class="text-right">{{ $oo->active ? 'YES' : 'NO' }}</td>
<td class="text-right">{{ number_format($oo->setup_cost_taxable,2) }}</td> <td class="text-right">{{ number_format($oo->setup_cost_taxable,2) }}</td>
<td class="text-right">{{ number_format($oo->base_cost_taxable,2) }}</td> <td class="text-right">{{ number_format($oo->base_cost_taxable,2) }}</td>
<td class="text-right">{{ number_format($oo->types->count()) }}</td> <td class="text-right">{{ number_format($oo->products->count()) }}</td>
<td class="text-right">{{ number_format($oo->types->pluck('product')->filter()->count()) }}</td> <td class="text-right">{{ number_format($oo->products->pluck('product')->filter()->count()) }}</td>
<td class="text-right">{{ number_format(($x=$oo->types->pluck('product.services')->flatten()->filter())->count()) }}</td> <td class="text-right">{{ number_format(($x=$oo->products->pluck('product.services')->flatten()->filter())->count()) }}</td>
<td class="text-right">{{ number_format($x->where('active')->count()) }}</td> <td class="text-right">{{ number_format($x->where('active')->count()) }}</td>
</tr> </tr>
@endforeach @endforeach
@ -53,7 +53,7 @@
<tr> <tr>
<th colspan="3">TOTALS</th> <th colspan="3">TOTALS</th>
<td class="text-right" colspan="3">{{ $xx->where('active',TRUE)->count() }}</td> <td class="text-right" colspan="3">{{ $xx->where('active',TRUE)->count() }}</td>
<td class="text-right">{{ number_format(($x=$xx->pluck('types')->flatten()->filter())->count()) }}</td> <td class="text-right">{{ number_format(($x=$xx->pluck('products')->flatten()->filter())->count()) }}</td>
<td class="text-right">{{ number_format($x->pluck('product')->filter()->count()) }}</td> <td class="text-right">{{ number_format($x->pluck('product')->filter()->count()) }}</td>
<td class="text-right">{{ number_format(($xxx=$x->pluck('product.services')->flatten()->filter())->count()) }}</td> <td class="text-right">{{ number_format(($xxx=$x->pluck('product.services')->flatten()->filter())->count()) }}</td>
<td class="text-right">{{ number_format($xxx->where('active')->count()) }}</td> <td class="text-right">{{ number_format($xxx->where('active')->count()) }}</td>
@ -72,7 +72,7 @@
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
@foreach($o->offeringTypes() as $key => $offering) @foreach(\App\Models\Supplier::offeringTypes($o) as $key => $offering)
$('#offering-{{ $key }}-table').DataTable(); $('#offering-{{ $key }}-table').DataTable();
$('#offering-{{ $key }}-table tbody').on('click','tr', function () { $('#offering-{{ $key }}-table tbody').on('click','tr', function () {

View File

@ -2,7 +2,7 @@
<div class="row"> <div class="row">
<div class="col-5 col-sm-2"> <div class="col-5 col-sm-2">
<div class="nav flex-column nav-tabs h-100" role="tablist" aria-orientation="vertical"> <div class="nav flex-column nav-tabs h-100" role="tablist" aria-orientation="vertical">
@foreach($o->offeringTypes() as $key => $offering) @foreach(\App\Models\Supplier::offeringTypes($o) as $key => $offering)
<a class="nav-link @if($loop->first)active @endif" id="products-{{ $key }}-tab" data-toggle="pill" href="#products-{{ $key }}-profile" role="tab" aria-controls="products-{{ $key }}-tab" aria-selected="true">{{ $offering->type }}</a> <a class="nav-link @if($loop->first)active @endif" id="products-{{ $key }}-tab" data-toggle="pill" href="#products-{{ $key }}-profile" role="tab" aria-controls="products-{{ $key }}-tab" aria-selected="true">{{ $offering->type }}</a>
@endforeach @endforeach
</div> </div>
@ -10,7 +10,7 @@
<div class="col-7 col-sm-10"> <div class="col-7 col-sm-10">
<div class="tab-content"> <div class="tab-content">
@foreach($o->offeringTypes() as $key => $offering) @foreach(\App\Models\Supplier::offeringTypes($o) as $key => $offering)
<div class="tab-pane text-left fade show @if($loop->first)active @endif" id="products-{{ $key }}-profile" role="tabpanel" aria-labelledby="products-{{ $key }}-tab"> <div class="tab-pane text-left fade show @if($loop->first)active @endif" id="products-{{ $key }}-profile" role="tabpanel" aria-labelledby="products-{{ $key }}-tab">
<table class="table table-sm table-bordered w-100" id="products-{{ $key }}-table"> <table class="table table-sm table-bordered w-100" id="products-{{ $key }}-table">
<thead> <thead>
@ -34,11 +34,11 @@
</thead> </thead>
<tbody> <tbody>
@foreach($xx=$offering->items->with(['types.product.services','types.product.type.supplied','types.product.description'])->get() as $oo) @foreach($xx=$offering->items->with(['products.product.services','products.product.type.supplied','products.product.description'])->get() as $oo)
@foreach($oo->types->pluck('product')->filter() as $po) @foreach($oo->products->pluck('product')->filter() as $po)
<tr> <tr>
<td>{{ $po->lid }}</td> <td><a href="{{ url('a/supplier/product/addedit',[$po->supplier->id,$po->supplied->id,$po->supplied->category]) }}">{{ $po->lid }}</a></td>
<td>{{ $po->name_short }}</td> <td>{{ $po->name_short }}</td>
<td>{{ $po->name }}</td> <td>{{ $po->name }}</td>
<td class="text-right">{{ $po->active ? 'YES' : 'NO' }}</td> <td class="text-right">{{ $po->active ? 'YES' : 'NO' }}</td>
@ -59,7 +59,7 @@
<th colspan="3">TOTALS</th> <th colspan="3">TOTALS</th>
<td class="text-right">{{ $xx->where('active',TRUE)->count() }}</td> <td class="text-right">{{ $xx->where('active',TRUE)->count() }}</td>
<th colspan="5">&nbsp;</th> <th colspan="5">&nbsp;</th>
<td class="text-right">{{ number_format(($xxx=$xx->pluck('types')->flatten()->pluck('product.services')->flatten()->filter())->count()) }}</td> <td class="text-right">{{ number_format(($xxx=$xx->pluck('products')->flatten()->pluck('product.services')->flatten()->filter())->count()) }}</td>
<td class="text-right">{{ number_format($xxx->where('active')->count()) }}</td> <td class="text-right">{{ number_format($xxx->where('active')->count()) }}</td>
</tr> </tr>
</tfoot> </tfoot>
@ -76,7 +76,7 @@
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
@foreach($o->offeringTypes() as $key => $offering) @foreach(\App\Models\Supplier::offeringTypes($o) as $key => $offering)
$('#products-{{ $key }}-table').DataTable(); $('#products-{{ $key }}-table').DataTable();
$('#products-{{ $key }}-table tbody').on('click','tr', function () { $('#products-{{ $key }}-table tbody').on('click','tr', function () {

View File

@ -44,7 +44,7 @@
@endif @endif
<tr> <tr>
<th>Speed</th> <th>Speed</th>
<td>{{ $o->supplied()->speed() }} Mbps</td> <td>{{ $o->supplied()->speed }} Mbps</td>
</tr> </tr>
<!-- @todo --> <!-- @todo -->
<tr> <tr>

View File

@ -1,21 +1,21 @@
<li class="nav-header">MAIN</li> <li class="nav-header">MAIN</li>
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('home') }}" class="nav-link @if(preg_match('#^event/summary#',Route::current()->uri())) active @endif"> <a href="{{ url('home') }}" class="nav-link @if(preg_match('#^u/home$#',$path=request()->path())) active @endif">
<i class="nav-icon fas fa-home"></i> <p>Home</p> <i class="nav-icon fas fa-home"></i> <p>Home</p>
</a> </a>
</li> </li>
<!-- ORDERS --> <!-- ORDERS -->
<li class="nav-item has-treeview @if(preg_match('#^order/#',Route::current()->uri())) menu-open @endif"> <li class="nav-item has-treeview @if(preg_match('#^order#',$path)) menu-open @endif">
<a href="#" class="nav-link @if(preg_match('#^order/#',Route::current()->uri())) active @endif"> <a href="#" class="nav-link @if(preg_match('#^order#',$path)) active @endif">
<i class="nav-icon fas fa-cash-register"></i> <i class="nav-icon fas fa-cash-register"></i>
<p>Orders <i class="fas fa-angle-left right"></i></p> <p>Orders <i class="fas fa-angle-left right"></i></p>
</a> </a>
<ul class="nav nav-treeview"> <ul class="nav nav-treeview">
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('order') }}" class="nav-link @if(Route::current()->uri() == 'group/add') active @endif"> <a href="{{ url('order') }}" class="nav-link @if(preg_match('#^order$#',$path)) active @endif">
<i class="fas fa-tag nav-icon"></i> <p>New Order</p> <i class="fas fa-tag nav-icon"></i> <p>New Order</p>
</a> </a>
</li> </li>
@ -24,21 +24,21 @@
@can('wholesaler') @can('wholesaler')
<!-- CHARGES --> <!-- CHARGES -->
<li class="nav-item has-treeview @if(preg_match('#^charge/#',Route::current()->uri())) menu-open @endif"> <li class="nav-item has-treeview @if(preg_match('#^a/charge/#',$path)) menu-open @endif">
<a href="#" class="nav-link @if(preg_match('#^charge/#',Route::current()->uri())) active @endif"> <a href="#" class="nav-link @if(preg_match('#^a/charge/#',$path)) active @endif">
<i class="nav-icon fas fa-plus"></i> <i class="nav-icon fas fa-plus"></i>
<p>Charges <i class="fas fa-angle-left right"></i></p> <p>Charges <i class="fas fa-angle-left right"></i></p>
</a> </a>
<ul class="nav nav-treeview"> <ul class="nav nav-treeview">
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('a/charge/addedit') }}" class="nav-link @if(Route::current()->uri() == 'charge/addedit') active @endif"> <a href="{{ url('a/charge/addedit') }}" class="nav-link @if(request()->path() == 'a/charge/addedit') active @endif">
<i class="fas fa-cart-plus nav-icon"></i> <p>New Charge</p> <i class="fas fa-cart-plus nav-icon"></i> <p>New Charge</p>
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('a/charge/unprocessed') }}" class="nav-link @if(Route::current()->uri() == 'charge/unprocessed') active @endif"> <a href="{{ url('a/charge/unprocessed') }}" class="nav-link @if(request()->path() == 'a/charge/unprocessed') active @endif">
<i class="fas fa-list nav-icon"></i> <p>Unprocessed</p> <i class="fas fa-list nav-icon"></i> <p>Unprocessed</p>
</a> </a>
</li> </li>
@ -46,21 +46,21 @@
</li> </li>
<!-- PAYMENTS --> <!-- PAYMENTS -->
<li class="nav-item has-treeview @if(preg_match('#^payment/#',Route::current()->uri())) menu-open @endif"> <li class="nav-item has-treeview @if(preg_match('#^a/payment/#',$path)) menu-open @endif">
<a href="#" class="nav-link @if(preg_match('#^payment/#',Route::current()->uri())) active @endif"> <a href="#" class="nav-link @if(preg_match('#^a/payment/#',$path)) active @endif">
<i class="nav-icon fas fa-receipt"></i> <i class="nav-icon fas fa-receipt"></i>
<p>Payments <i class="fas fa-angle-left right"></i></p> <p>Payments <i class="fas fa-angle-left right"></i></p>
</a> </a>
<ul class="nav nav-treeview"> <ul class="nav nav-treeview">
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('a/payment/addedit') }}" class="nav-link @if(Route::current()->uri() == 'payment/addedit') active @endif"> <a href="{{ url('a/payment/addedit') }}" class="nav-link @if(preg_match('#^a/payment/addedit#',$path)) active @endif">
<i class="fas fa-money-bill nav-icon"></i> <p>New Payment</p> <i class="fas fa-money-bill nav-icon"></i> <p>New Payment</p>
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('a/payment/unapplied') }}" class="nav-link @if(Route::current()->uri() == 'payment/unapplied') active @endif"> <a href="{{ url('a/payment/unapplied') }}" class="nav-link @if(preg_match('#^a/payment/unapplied$#',$path)) active @endif">
<i class="fas fa-receipt nav-icon"></i> <p>Unapplied</p> <i class="fas fa-receipt nav-icon"></i> <p>Unapplied</p>
</a> </a>
</li> </li>
@ -72,31 +72,48 @@
<li class="nav-header">ADMIN</li> <li class="nav-header">ADMIN</li>
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('a/setup') }}" class="nav-link @if(preg_match('#^a/setup#',Route::current()->uri())) active @endif"> <a href="{{ url('a/setup') }}" class="nav-link @if(preg_match('#^a/setup#',$path)) active @endif">
<i class="nav-icon fas fa-cogs"></i> <p>Site Setup</p> <i class="nav-icon fas fa-cogs"></i> <p>Site Setup</p>
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('a/product') }}" class="nav-link @if(preg_match('#^a/product]#',Route::current()->uri())) active @endif"> <a href="{{ url('a/product') }}" class="nav-link @if(preg_match('#^a/product#',$path)) active @endif">
<i class="nav-icon fas fa-barcode"></i> <p>Products</p> <i class="nav-icon fas fa-barcode"></i> <p>Products</p>
</a> </a>
</li> </li>
<!-- SUPPLIERS -->
<li class="nav-item has-treeview @if(preg_match('#^a/supplier#',$path)) menu-open @endif">
<a href="#" class="nav-link @if(preg_match('#^a/supplier#',$path)) active @endif">
<i class="nav-icon fas fa-user-tag"></i>
<p>Suppliers <i class="fas fa-angle-left right"></i></p>
</a>
<ul class="nav nav-treeview">
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('a/supplier') }}" class="nav-link @if(preg_match('#^a/supplier#',Route::current()->uri())) active @endif"> <a href="{{ url('a/supplier') }}" class="nav-link @if(preg_match('#^a/supplier(/[cost|details]+/[0-9]+)?$#',$path)) active @endif">
<i class="nav-icon fas fa-user-tag"></i> <p>Suppliers</p> <i class="nav-icon fas fa-cogs"></i> <p>Configuration</p>
</a> </a>
</li> </li>
<li class="nav-item has-treeview @if(preg_match('#^a/report/(products)#',request()->path()))menu-open @else menu-closed @endif"> <li class="nav-item">
<a href="#" class="nav-link @if(preg_match('#^a/report/(products)#',request()->path())) active @endif"> <a href="{{ url('a/supplier/product/add') }}" class="nav-link @if(preg_match('#^a/supplier/product/(add|edit)#',$path)) active @endif">
<i class="nav-icon fas fa-barcode"></i> <p>New Product</p>
</a>
</li>
</ul>
</li>
<li class="nav-item has-treeview @if(preg_match('#^a/report/(products)#',$path))menu-open @else menu-closed @endif">
<a href="#" class="nav-link @if(preg_match('#^a/report/(products)#',$path)) active @endif">
<i class="nav-icon fas fa-list"></i> <p>REPORT<i class="fas fa-angle-left right"></i></p> <i class="nav-icon fas fa-list"></i> <p>REPORT<i class="fas fa-angle-left right"></i></p>
</a> </a>
<ul class="nav nav-treeview"> <ul class="nav nav-treeview">
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('a/report/products') }}" class="nav-link @if(preg_match('#^a/report/products$#',request()->path()))active @endif"> <a href="{{ url('a/report/products') }}" class="nav-link @if(preg_match('#^a/report/products$#',$path))active @endif">
<i class="nav-icon fas fa-barcode"></i> <p>Products</p> <i class="nav-icon fas fa-barcode"></i> <p>Products</p>
</a> </a>
</li> </li>
@ -107,26 +124,26 @@
@can('reseller') @can('reseller')
<li class="nav-header">RESELLER</li> <li class="nav-header">RESELLER</li>
<li class="nav-item has-treeview @if(preg_match('#^r/report/(domain|email|hosting)#',request()->path()))menu-open @else menu-closed @endif"> <li class="nav-item has-treeview @if(preg_match('#^r/report/(domain|email|hosting)#',$path))menu-open @else menu-closed @endif">
<a href="#" class="nav-link @if(preg_match('#^r/report/(domain|email|hosting)#',request()->path())) active @endif"> <a href="#" class="nav-link @if(preg_match('#^r/report/(domain|email|hosting)#',$path)) active @endif">
<i class="nav-icon fas fa-list"></i> <p>REPORT<i class="fas fa-angle-left right"></i></p> <i class="nav-icon fas fa-list"></i> <p>REPORT<i class="fas fa-angle-left right"></i></p>
</a> </a>
<ul class="nav nav-treeview"> <ul class="nav nav-treeview">
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('r/report/domain') }}" class="nav-link @if(preg_match('#^r/report/domain#',request()->path()))active @endif"> <a href="{{ url('r/report/domain') }}" class="nav-link @if(preg_match('#^r/report/domain$#',$path))active @endif">
<i class="nav-icon fas fa-globe-asia"></i> <p>Domain Names</p> <i class="nav-icon fas fa-globe-asia"></i> <p>Domain Names</p>
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('r/report/email') }}" class="nav-link @if(preg_match('#^r/report/email#',request()->path()))active @endif"> <a href="{{ url('r/report/email') }}" class="nav-link @if(preg_match('#^r/report/email$#',$path))active @endif">
<i class="nav-icon fas fa-envelope"></i> <p>Email Hosting</p> <i class="nav-icon fas fa-envelope"></i> <p>Email Hosting</p>
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="{{ url('r/report/hosting') }}" class="nav-link @if(preg_match('#^r/report/hosting#',request()->path()))active @endif"> <a href="{{ url('r/report/hosting') }}" class="nav-link @if(preg_match('#^r/report/hosting$#',$path))active @endif">
<i class="nav-icon fas fa-sitemap"></i> <p>Web Hosting</p> <i class="nav-icon fas fa-sitemap"></i> <p>Web Hosting</p>
</a> </a>
</li> </li>

View File

@ -14,6 +14,7 @@ use App\Http\Controllers\{AdminController,
ServiceController, ServiceController,
SupplierController, SupplierController,
WelcomeController}; WelcomeController};
use App\Models\Supplier;
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -72,6 +73,18 @@ Route::group(['middleware'=>['theme:adminlte-be','auth','role:wholesaler'],'pref
->where('o','[0-9]+'); ->where('o','[0-9]+');
Route::get('supplier/cost/{o}',[SupplierController::class,'cost']) Route::get('supplier/cost/{o}',[SupplierController::class,'cost'])
->where('o','[0-9]+'); ->where('o','[0-9]+');
Route::get('supplier/product/add',[SupplierController::class,'product_add']);
Route::get('supplier/product/addedit/{o}/{oo}/{type}',[SupplierController::class,'product_view'])
->where('o','[0-9]+')
->where('oo','[0-9]+')
->whereIn('type',Supplier::offeringTypeKeys()->toArray());
Route::post('supplier/product/addedit/{o}/{oo}/{type}',[SupplierController::class,'product_addedit'])
->where('o','[0-9]+')
->where('oo','[0-9]+')
->whereIn('type',Supplier::offeringTypeKeys()->toArray());
Route::post('supplier/product/view/{type}/{oo?}',[SupplierController::class,'product_view_type'])
->whereIn('type',Supplier::offeringTypeKeys()->toArray())
->where('oo','[0-9]+');
//Route::get('report/products','Wholesale\ReportController@products'); //Route::get('report/products','Wholesale\ReportController@products');
// Charges - @todo This should probably go to resellers // Charges - @todo This should probably go to resellers