diff --git a/app/Http/Controllers/Wholesale/ReportController.php b/app/Http/Controllers/Wholesale/ReportController.php
new file mode 100644
index 0000000..e7d80b7
--- /dev/null
+++ b/app/Http/Controllers/Wholesale/ReportController.php
@@ -0,0 +1,13 @@
+'extra_up_offpeak',
+ 'base_down_offpeak'=>'extra_down_offpeak',
+ 'base_up_peak'=>'extra_up_peak',
+ 'base_down_peak'=>'extra_down_peak',
+ ]);
+
+ // Map the NULL relationships - and where traffic gets applied if NULL
+ $merge = collect([
+ 'extra_up_offpeak'=>'base_down_offpeak',
+ 'extra_down_offpeak'=>'base_down_peak',
+ 'extra_up_peak'=>'base_down_peak',
+ 'extra_down_peak'=>'base_down_peak',
+ ]);
+
+ if (is_null($config))
+ $config = collect($config);
+
+ // If config is null, use the configuration from this Model
+ if (! $config->count()) {
+ // Base Config
+ foreach ($map->keys() as $k) {
+ $config->put($k,$this->{$k});
+ }
+
+ // Excess Config
+ foreach ($map->values() as $k) {
+ $config->put($k,$this->{$k});
+ }
+
+ // Shaped or Charge
+ $config->put('shaped',$this->extra_shaped);
+ $config->put('charged',$this->extra_charged);
+
+ // Metric - used to round down data in $data.
+ $config->put('metric',$this->metric);
+ }
+
+ $result = collect();
+
+ // If data is empty, we'll report on allowance, otherwise we'll report on consumption
+ $report = $data ? FALSE : TRUE;
+
+ // Work out if we charge each period
+ foreach ($map as $k => $v) {
+ // Anything NULL is not counted
+ if (is_null($config->get($k)))
+ continue;
+
+ $x = $report ? $config->get($k) : ($config->get($k)-Arr::get($data,$k,0));
+
+ if ($ceil)
+ $x = (int)ceil($x);
+
+ // Non-NULL entries are counted as is
+ if (! is_null($config->get($v))) {
+ // Existing value for this item to be added
+ $value = $result->has($k) ? $result->get($k) : 0;
+ $result->put($k,$value+$x);
+
+ // NULL entries are merged into another key
+ } else {
+ // New Key for this item
+ $key = $merge->get($v);
+
+ // Existing value for this item to be added
+ $value = $result->has($key) ? $result->get($key) : 0;
+
+ // Any value in the existing key, add it too.
+ if ($k !== $key AND $result->has($k)) {
+ $value += $result->get($k);
+ $result->forget($k);
+ }
+
+ $result->put($key,$value+$x);
+ }
+ }
+
+ if ($config->has('metric') AND $config->get('metric'))
+ $result->transform(function($item) use ($config,$ceil) {
+ return $ceil
+ ? (int)ceil($item/$config->get('metric'))
+ : $item/$config->get('metric');
+ });
+
+ return $result;
+ }
+
public function getNameAttribute()
{
return $this->speed;
diff --git a/app/Models/Product.php b/app/Models/Product.php
index 70c6b0d..af6d3a6 100644
--- a/app/Models/Product.php
+++ b/app/Models/Product.php
@@ -7,6 +7,7 @@ use Illuminate\Support\Arr;
use Illuminate\Http\Request;
use App\Traits\NextKey;
+use Illuminate\Support\Facades\Log;
class Product extends Model
{
@@ -132,7 +133,12 @@ class Product extends Model
public function getPriceArrayAttribute()
{
- return unserialize($this->attributes['price_group']);
+ try {
+ return unserialize($this->attributes['price_group']);
+ } catch (\Exception $e) {
+ Log::debug('Problem with Price array in product ',['pid'=>$this->id]);
+ return [];
+ }
}
public function getPriceTypeAttribute()
diff --git a/app/Models/Product/Adsl.php b/app/Models/Product/Adsl.php
index ce7fb99..3df3ed3 100644
--- a/app/Models/Product/Adsl.php
+++ b/app/Models/Product/Adsl.php
@@ -2,30 +2,65 @@
namespace App\Models\Product;
-use App\Traits\NextKey;
-use App\Models\AdslSupplierPlan;
+use Illuminate\Support\Collection;
-class Adsl extends \App\Models\Base\ProductType
+use App\Interfaces\ProductSupplier;
+use App\Models\Base\ProductType;
+use App\Models\AdslSupplier;
+use App\Models\AdslSupplierPlan;
+use App\Traits\NextKey;
+
+class Adsl extends ProductType implements ProductSupplier
{
use NextKey;
-
const RECORD_ID = 'adsl_plan';
protected $table = 'ab_adsl_plan';
+ public static $map = [
+ 'base_up_offpeak'=>'extra_up_offpeak',
+ 'base_down_offpeak'=>'extra_down_offpeak',
+ 'base_up_peak'=>'extra_up_peak',
+ 'base_down_peak'=>'extra_down_peak',
+ ];
+
+ /**
+ * Map upstream metrics into traffic allowance metrics
+ *
+ * @var array
+ */
+ public static $metrics = [
+ 'down_peak'=>'base_down_peak',
+ 'down_offpeak'=>'base_down_offpeak',
+ 'up_peak'=>'base_up_peak',
+ 'up_offpeak'=>'base_up_offpeak',
+ 'peer'=>'base_down_peak',
+ 'internal'=>'base_down_offpeak',
+ ];
+
+ /**
+ * The suppliers product
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasOne
+ */
public function product()
{
return $this->hasOne(AdslSupplierPlan::class,'id','adsl_supplier_plan_id');
}
+ /**
+ * The supplier
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
+ */
+ public function supplier()
+ {
+ return $this->hasOneThrough(AdslSupplier::class,AdslSupplierPlan::class,'id','id','adsl_supplier_plan_id','supplier_id');
+ }
+
public function __get($key)
{
- switch($key)
- {
- case 'base_down_peak':
- return $this->attributes['base_down_peak']/$this->attributes['metric'];
- case 'base_down_peak':
- return $this->attributes['base_down_offpeak']/$this->attributes['metric'];
+ switch($key) {
case 'speed':
return $this->product->speed;
}
@@ -33,4 +68,87 @@ class Adsl extends \App\Models\Base\ProductType
// If we dont have a specific key, we'll resolve it normally
return parent::__get($key);
}
+
+ /** ATTRIBUTES **/
+
+ /**
+ * Calculate the allowance array or traffic used array
+ *
+ * @param array Traffic Used in each metric.
+ * @param bool $ceil Round the numbers to integers
+ * @return array|string
+ */
+ public function allowance(array $data=[],bool $ceil=TRUE): Collection
+ {
+ $config = collect();
+
+ // Base Config
+ foreach (array_keys(static::$map) as $k) {
+ $config->put($k,$this->{$k});
+ }
+
+ // Excess Config
+ foreach (array_values(static::$map) as $k) {
+ $config->put($k,$this->{$k});
+ }
+
+ // Shaped or Charge
+ $config->put('shaped',$this->extra_shaped);
+ $config->put('charged',$this->extra_charged);
+
+ // Metric - used to round down data in $data.
+ $config->put('metric',$this->metric);
+
+ return $this->product->allowance($config,$data,$ceil);
+ }
+
+ /**
+ * Return the suppliers cost for this service
+ *
+ * @return float
+ */
+ public function allowance_cost(): float
+ {
+ $result = 0;
+ foreach ($this->product->allowance(NULL,$this->allowance([])->toArray()) as $k=>$v) {
+ $result += -$v*$this->product->{static::$map[$k]};
+ }
+
+ return $result;
+ }
+
+ /**
+ * Render the allowance as a string
+ * eg: 50/100
+ *
+ * @return string
+ */
+ public function allowance_string(): string
+ {
+ $result = '';
+ $data = $this->allowance();
+
+ foreach ([
+ 'base_down_peak',
+ 'base_up_peak',
+ 'base_down_offpeak',
+ 'base_up_offpeak',
+ ] as $k)
+ {
+ if ($data->has($k)) {
+ if ($result)
+ $result .= '/';
+
+ $result .= $data->get($k);
+ }
+ }
+
+ return $result;
+ }
+
+ public function getCostAttribute(): float
+ {
+ // @todo Tax shouldnt be hard coded
+ return ($this->product->base_cost+$this->allowance_cost())*1.1;
+ }
}
\ No newline at end of file
diff --git a/app/Models/Product/SSL.php b/app/Models/Product/SSL.php
index f0968b4..4bc36b5 100644
--- a/app/Models/Product/SSL.php
+++ b/app/Models/Product/SSL.php
@@ -2,13 +2,34 @@
namespace App\Models\Product;
+use Illuminate\Support\Collection;
+
+use App\Interfaces\ProductSupplier;
+use App\Models\Base\ProductType;
use App\Traits\NextKey;
-class SSL extends \App\Models\Base\ProductType
+class SSL extends ProductType implements ProductSupplier
{
use NextKey;
-
const RECORD_ID = 'ssl';
protected $table = 'ab_ssl';
+
+ public function allowance(): Collection
+ {
+ // N/A
+ return collect();
+ }
+
+ public function allowance_string(): string
+ {
+ // N/A
+ return '';
+ }
+
+ public function getCostAttribute(): float
+ {
+ // N/A
+ return 0;
+ }
}
\ No newline at end of file
diff --git a/app/Models/Service.php b/app/Models/Service.php
index dcbef10..1c56637 100644
--- a/app/Models/Service.php
+++ b/app/Models/Service.php
@@ -301,6 +301,22 @@ class Service extends Model
return $this->addTax(is_null($this->price) ? $this->product->price($this->recur_schedule) : $this->price);
}
+ public function getBillingMonthlyPriceAttribute(): float
+ {
+ $d = 0;
+ switch ($this->recur_schedule) {
+ case 0: $d = 12/52; break;
+ case 1: $d = 1; break;
+ case 2: $d = 3; break;
+ case 3: $d = 6; break;
+ case 4: $d = 12; break;
+ case 5: $d = 24; break;
+ case 6: $d = 36; break;
+ }
+
+ return number_format($this->getBillingPriceAttribute()/$d,2);
+ }
+
/**
* Return the service billing period
*
diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php
index 7527c26..39ea383 100644
--- a/app/Providers/AuthServiceProvider.php
+++ b/app/Providers/AuthServiceProvider.php
@@ -8,24 +8,28 @@ use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvid
class AuthServiceProvider extends ServiceProvider
{
- /**
- * The policy mappings for the application.
- *
- * @var array
- */
- protected $policies = [
- 'App\Model' => 'App\Policies\ModelPolicy',
- ];
+ /**
+ * The policy mappings for the application.
+ *
+ * @var array
+ */
+ protected $policies = [
+ 'App\Model' => 'App\Policies\ModelPolicy',
+ ];
- /**
- * Register any authentication / authorization services.
- *
- * @return void
- */
- public function boot()
- {
- $this->registerPolicies();
- Passport::routes();
- // Passport::enableImplicitGrant();
- }
-}
+ /**
+ * Register any authentication / authorization services.
+ *
+ * @return void
+ */
+ public function boot()
+ {
+ $this->registerPolicies();
+ Passport::routes();
+ // Passport::enableImplicitGrant();
+
+ Gate::define('wholesaler', function ($user) {
+ return $user->isWholesaler();
+ });
+ }
+}
\ No newline at end of file
diff --git a/database/factories/AdslPlanFactory.php b/database/factories/AdslPlanFactory.php
new file mode 100644
index 0000000..2578a35
--- /dev/null
+++ b/database/factories/AdslPlanFactory.php
@@ -0,0 +1,86 @@
+define(App\Models\Product\Adsl::class, function (Faker $faker) {
+ return [
+ 'id'=>1,
+ 'contract_term'=>12,
+ ];
+});
+
+$factory->afterMaking(App\Models\Product\Adsl::class, function ($adsl,$faker) {
+ $product = factory(App\Models\AdslSupplierPlan::class)->make();
+ $adsl->setRelation('product',$product);
+ $adsl->adsl_supplier_plan_id = $product->id;
+});
+
+$factory->state(App\Models\Product\Adsl::class,'unlimit',[
+ 'base_down_peak'=>NULL,
+ 'base_up_peak'=>NULL,
+ 'base_down_offpeak'=>NULL,
+ 'base_up_offpeak'=>NULL,
+ 'extra_charged'=>NULL,
+ 'extra_shaped'=>NULL,
+ 'extra_down_peak'=>NULL,
+ 'extra_up_peak'=>NULL,
+ 'extra_down_offpeak'=>NULL,
+ 'extra_up_offpeak'=>NULL,
+ 'metric'=>1,
+]);
+
+$factory->state(App\Models\Product\Adsl::class,'140/0/0/0',[
+ 'base_down_peak'=>140,
+ 'base_up_peak'=>0,
+ 'base_down_offpeak'=>0,
+ 'base_up_offpeak'=>0,
+ 'extra_charged'=>NULL,
+ 'extra_shaped'=>NULL,
+ 'extra_down_peak'=>1,
+ 'extra_up_peak'=>NULL,
+ 'extra_down_offpeak'=>NULL,
+ 'extra_up_offpeak'=>NULL,
+ 'metric'=>1,
+]);
+
+$factory->state(App\Models\Product\Adsl::class,'70/-/0/-',[
+ 'base_down_peak'=>70,
+ 'base_up_peak'=>NULL,
+ 'base_down_offpeak'=>0,
+ 'base_up_offpeak'=>NULL,
+ 'extra_charged'=>NULL,
+ 'extra_shaped'=>NULL,
+ 'extra_down_peak'=>1,
+ 'extra_up_peak'=>NULL,
+ 'extra_down_offpeak'=>NULL,
+ 'extra_up_offpeak'=>NULL,
+ 'metric'=>1,
+]);
+
+$factory->state(App\Models\Product\Adsl::class,'100/0/40/0',[
+ 'base_down_peak'=>100,
+ 'base_up_peak'=>0,
+ 'base_down_offpeak'=>40,
+ 'base_up_offpeak'=>0,
+ 'extra_charged'=>NULL,
+ 'extra_shaped'=>NULL,
+ 'extra_down_peak'=>0,
+ 'extra_up_peak'=>NULL,
+ 'extra_down_offpeak'=>0,
+ 'extra_up_offpeak'=>NULL,
+ 'metric'=>1,
+]);
+
+$factory->state(App\Models\Product\Adsl::class,'50/-/20/-',[
+ 'base_down_peak'=>50,
+ 'base_up_peak'=>NULL,
+ 'base_down_offpeak'=>20,
+ 'base_up_offpeak'=>NULL,
+ 'extra_charged'=>NULL,
+ 'extra_shaped'=>NULL,
+ 'extra_down_peak'=>0,
+ 'extra_up_peak'=>NULL,
+ 'extra_down_offpeak'=>0,
+ 'extra_up_offpeak'=>NULL,
+ 'metric'=>1,
+]);
\ No newline at end of file
diff --git a/database/factories/AdslSupplierPlanFactory.php b/database/factories/AdslSupplierPlanFactory.php
new file mode 100644
index 0000000..4cfb7ad
--- /dev/null
+++ b/database/factories/AdslSupplierPlanFactory.php
@@ -0,0 +1,10 @@
+define(App\Models\AdslSupplierPlan::class, function (Faker $faker) {
+ return [
+ 'id'=>1,
+ 'contract_term'=>12,
+ ];
+});
\ No newline at end of file
diff --git a/database/factories/ProductFactory.php b/database/factories/ProductFactory.php
index 5a6d984..3d11846 100644
--- a/database/factories/ProductFactory.php
+++ b/database/factories/ProductFactory.php
@@ -26,4 +26,39 @@ $factory->state(App\Models\Product::class,'strict',[
]);
$factory->state(App\Models\Product::class,'notstrict',[
'price_recurr_strict' => 0,
-]);
\ No newline at end of file
+]);
+
+$factory->afterMakingState(App\Models\Product::class,'broadband-unlimit',function ($product,$faker) {
+ $type = factory(App\Models\Product\Adsl::class)->state('unlimit')->make();
+ $product->setRelation('type',$type);
+ $product->prod_plugin_data = $type->id;
+ $product->model = 'App\Models\Product\Adsl';
+});
+
+$factory->afterMakingState(App\Models\Product::class,'broadband-140/0/0/0',function ($product,$faker) {
+ $type = factory(App\Models\Product\Adsl::class)->state('140/0/0/0')->make();
+ $product->setRelation('type',$type);
+ $product->prod_plugin_data = $type->id;
+ $product->model = 'App\Models\Product\Adsl';
+});
+
+$factory->afterMakingState(App\Models\Product::class,'broadband-70/-/0/-',function ($product,$faker) {
+ $type = factory(App\Models\Product\Adsl::class)->state('70/-/0/-')->make();
+ $product->setRelation('type',$type);
+ $product->prod_plugin_data = $type->id;
+ $product->model = 'App\Models\Product\Adsl';
+});
+
+$factory->afterMakingState(App\Models\Product::class,'broadband-100/0/40/0',function ($product,$faker) {
+ $type = factory(App\Models\Product\Adsl::class)->state('100/0/40/0')->make();
+ $product->setRelation('type',$type);
+ $product->prod_plugin_data = $type->id;
+ $product->model = 'App\Models\Product\Adsl';
+});
+
+$factory->afterMakingState(App\Models\Product::class,'broadband-50/-/20/-',function ($product,$faker) {
+ $type = factory(App\Models\Product\Adsl::class)->state('50/-/20/-')->make();
+ $product->setRelation('type',$type);
+ $product->prod_plugin_data = $type->id;
+ $product->model = 'App\Models\Product\Adsl';
+});
\ No newline at end of file
diff --git a/resources/theme/backend/adminlte/a/product/report.blade.php b/resources/theme/backend/adminlte/a/product/report.blade.php
new file mode 100644
index 0000000..11a4bff
--- /dev/null
+++ b/resources/theme/backend/adminlte/a/product/report.blade.php
@@ -0,0 +1,106 @@
+@extends('adminlte::layouts.app')
+
+@section('htmlheader_title')
+ Product List
+@endsection
+@section('page_title')
+ Product List
+@endsection
+
+@section('contentheader_title')
+ Product List
+@endsection
+@section('contentheader_description')
+@endsection
+
+@section('main-content')
+
+
+
+
+ ID |
+ Service |
+ Product |
+ Monthly |
+ Cost |
+ Traffic |
+
+
+
+
+ @foreach (\App\Models\Service::active()->get() as $o)
+
+ {{ $o->id }} |
+ {{ $o->sname }} |
+ {{ $o->product->name }} |
+ {{ number_format($o->billing_monthly_price,2) }} |
+ {{ $o->product->type ? number_format($o->product->type->cost,2) : 'NO TYPE' }} |
+ {{ $o->product->type ? $o->product->type->allowance_string() : '-' }} |
+
+ @endforeach
+
+
+
+@endsection
+
+@section('page-scripts')
+ @css('//cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css','datatables-css')
+ @js('//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js','datatables-js')
+ @css('//cdn.datatables.net/responsive/2.2.1/css/responsive.dataTables.min.css','datatables-responsive-css')
+ @js('//cdn.datatables.net/responsive/2.2.1/js/dataTables.responsive.min.js','datatables-responsive-js')
+ @css('//cdn.datatables.net/rowgroup/1.1.0/css/rowGroup.dataTables.min.css','datatables-rowgroup-css')
+ @js('//cdn.datatables.net/rowgroup/1.1.0/js/dataTables.rowGroup.min.js','datatables-rowgroup-js')
+ @css('//cdn.datatables.net/buttons/1.5.6/css/buttons.dataTables.min.css','datatables-button-css')
+ @js('//cdn.datatables.net/buttons/1.5.6/js/dataTables.buttons.min.js','datatables-button-js')
+ @css('//cdn.datatables.net/fixedheader/3.1.5/css/fixedHeader.dataTables.min.css','datatables-fixed-css')
+ @js('//cdn.datatables.net/fixedheader/3.1.5/js/dataTables.fixedHeader.min.js','datatables-fixed-js')
+ @css('/plugin/dataTables/dataTables.bootstrap4.css','datatables-bootstrap4-css')
+ @js('/plugin/dataTables/dataTables.bootstrap4.js','datatables-bootstrap4-js')
+ @css('/plugin/dataTables/dataTables.bootstrap4.css','datatables-bootstrap4-css')
+ @js('//cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js','jszip')
+ @js('//cdn.datatables.net/buttons/1.5.6/js/buttons.html5.min.js','datatables-buttons-html5')
+
+
+
+
+@append
\ No newline at end of file
diff --git a/resources/theme/backend/adminlte/a/service/widget/internal.blade.php b/resources/theme/backend/adminlte/a/service/widget/internal.blade.php
new file mode 100644
index 0000000..6120612
--- /dev/null
+++ b/resources/theme/backend/adminlte/a/service/widget/internal.blade.php
@@ -0,0 +1,28 @@
+
+
+ Supplier | {{ $o->product->type->supplier->name }} |
+
+
+ Supplier Product | #{{ $o->product_id }}: {{ $o->product->type->product->product_id }} |
+
+
+
+ Supplier Setup | ${{ number_format($o->product->type->product->setup_cost*1.1,2) }} |
+
+
+ Supplier Cost | ${{ number_format($o->product->type->cost,2) }} |
+
+
+ Supplier Contract | {{ $o->product->type->product->contract_term }} months |
+
+
+
+ Supplier Min Cost | ${{ number_format((($x=$o->product->type->product)->setup_cost+$x->base_cost*$x->contract_term)*1.1,2) }} |
+
+
+ Price | ${{ number_format($o->billing_monthly_price,2) }} |
+
+
+ Markup | {{ number_format(($o->billing_monthly_price/$o->product->type->cost-1)*100,2) }}% |
+
+
\ No newline at end of file
diff --git a/resources/theme/backend/adminlte/u/service.blade.php b/resources/theme/backend/adminlte/u/service.blade.php
index cb3a94e..52367ca 100644
--- a/resources/theme/backend/adminlte/u/service.blade.php
+++ b/resources/theme/backend/adminlte/u/service.blade.php
@@ -26,16 +26,19 @@