diff --git a/app/Console/Commands/ServiceList.php b/app/Console/Commands/ServiceList.php
index 7a16f9c..601e321 100644
--- a/app/Console/Commands/ServiceList.php
+++ b/app/Console/Commands/ServiceList.php
@@ -3,10 +3,9 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
-use Illuminate\Support\Facades\DB;
-use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Config;
-use App\Models\Service;
+use App\Models\{Service,Site};
class ServiceList extends Command
{
@@ -15,10 +14,10 @@ class ServiceList extends Command
*
* @var string
*/
- protected $signature = 'service:list '.
- '{--a|active : Active Only}'.
- '{--category= : Category}'.
- '{--f|fix : Fix start_date}';
+ protected $signature = 'service:list'.
+ ' {--i|inactive : Include Inactive}'.
+ ' {--t|type= : Type}'.
+ ' {--f|fix : Fix start_date}';
/**
* The console command description.
@@ -27,16 +26,6 @@ class ServiceList extends Command
*/
protected $description = 'List all services';
- /**
- * Create a new command instance.
- *
- * @return void
- */
- public function __construct()
- {
- parent::__construct();
- }
-
/**
* Execute the console command.
*
@@ -44,13 +33,11 @@ class ServiceList extends Command
*/
public function handle()
{
- DB::listen(function($query) {
- Log::debug('- SQL',['sql'=>$query->sql,'binding'=>$query->bindings]);
- });
+ $header = '|%13s|%-10s|%-35s|%-40s|%8s|%10s|%15s|%10s|%10s|%12s|%14s|';
- $this->warn(sprintf('|%10s|%-6s|%-20s|%-50s|%8s|%14s|%10s|%10s|%10s|%10s|%10s|',
+ $this->warn(sprintf($header,
'ID',
- 'CAT',
+ 'Type',
'Product',
'Name',
'active',
@@ -60,13 +47,15 @@ class ServiceList extends Command
'stop date',
'connect date',
'first invoice'
- ));
+ ));
- foreach (Service::all() as $o) {
- if ($this->option('active') AND ! $o->isActive())
+ foreach (Service::withoutGlobalScope(\App\Models\Scopes\SiteScope::class)->with(['site'])->cursor() as $o) {
+ if ((! $this->option('inactive')) AND ! $o->isActive())
continue;
- if ($this->option('category') AND $o->product->category !== $this->option('category'))
+ Config::set('site',$o->site);
+
+ if ($this->option('type') AND ($o->product->getProductTypeAttribute() !== $this->option('type')))
continue;
$c = $o->invoice_items->filter(function($item) {return $item->item_type === 0; })->sortby('date_start')->first();
@@ -76,10 +65,10 @@ class ServiceList extends Command
$o->save();
}
- $this->info(sprintf('|%10s|%-6s|%-20s|%-50s|%8s|%14s|%10s|%10s|%10s|%10s|%10s|',
+ $this->info(sprintf($header,
$o->sid,
- $o->product->category,
- $o->product_name,
+ $o->product->getProductTypeAttribute(),
+ $o->product->getNameAttribute(),
$o->name_short,
$o->active ? 'active' : 'inactive',
$o->status,
@@ -87,7 +76,7 @@ class ServiceList extends Command
$o->date_start ? $o->date_start->format('Y-m-d') : NULL,
$o->date_end ? $o->date_end->format('Y-m-d') : NULL,
($o->type AND $o->type->service_connect_date) ? $o->type->service_connect_date->format('Y-m-d') : NULL,
- $c ? $c->date_start->format('Y-m-d') : NULL,
+ ($c && $c->date_start) ? $c->date_start->format('Y-m-d') : NULL,
));
}
}
diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php
index 37aa591..29cf8d4 100644
--- a/app/Http/Controllers/AdminController.php
+++ b/app/Http/Controllers/AdminController.php
@@ -170,7 +170,7 @@ class AdminController extends Controller
*/
public function supplier()
{
- return view('a.supplier');
+ return view('a.supplier.find');
}
/**
@@ -223,7 +223,7 @@ class AdminController extends Controller
if (! $o->exists && $request->name)
$o = Supplier::where('name',$request->name)->with(['details'])->firstOrNew();
- return view('a.supplierdetails')
+ return view('a.supplier.details')
->with('o',$o);
}
diff --git a/app/Http/Controllers/OrderController.php b/app/Http/Controllers/OrderController.php
index cbaa381..adc9982 100644
--- a/app/Http/Controllers/OrderController.php
+++ b/app/Http/Controllers/OrderController.php
@@ -14,16 +14,19 @@ use App\Models\{Account,Product,Service,User};
class OrderController extends Controller
{
+ // @todo To check
public function __construct()
{
$this->middleware('auth');
}
+ // @todo To check
public function index()
{
return view('order.home');
}
+ // @todo To check
public function product_order(Product $o)
{
Theme::set('metronic-fe');
@@ -31,6 +34,7 @@ class OrderController extends Controller
return view('order.widget.order',['o'=>$o]);
}
+ // @todo To check
public function product_info(Product $o)
{
Theme::set('metronic-fe');
@@ -38,9 +42,10 @@ class OrderController extends Controller
return view('order.widget.info',['o'=>$o]);
}
+ // @todo To check
public function submit(Request $request)
{
- Validator::make($request->all(),['product_id'=>'required|exists:ab_product,id'])
+ Validator::make($request->all(),['product_id'=>'required|exists:products,id'])
// Reseller
->sometimes('account_id','required|email',function($input) use ($request) {
return is_null($input->account_id) AND is_null($input->order_email_manual);
@@ -59,7 +64,7 @@ class OrderController extends Controller
$po = Product::findOrFail($request->input('product_id'));
// Check we have the custom attributes for the product
- $options = $po->orderValidation($request);
+ $order = $po->orderValidation($request);
if ($request->input('order_email_manual')) {
$uo = User::firstOrNew(['email'=>$request->input('order_email_manual')]);
@@ -101,24 +106,24 @@ class OrderController extends Controller
$so->product_id = $request->input('product_id');
$so->order_status = 'ORDER-SUBMIT';
$so->orderby_id = Auth::id();
- $so->model = get_class($options);
+ $so->model = get_class($order);
- if ($options->order_info) {
- $so->order_info = $options->order_info;
+ if ($order->order_info) {
+ $so->order_info = $order->order_info;
- unset($options->order_info);
+ unset($order->order_info);
}
$so = $ao->services()->save($so);
- if ($options instanceOf Model) {
- $options->service_id = $so->id;
- $options->save();
+ if ($order instanceOf Model) {
+ $order->service_id = $so->id;
+ $order->save();
}
Mail::to('help@graytech.net.au')
- ->queue((new OrderRequest($so,$request->input('options.notes','')))->onQueue('email')); //@todo Get email from DB.
+ ->queue((new OrderRequest($so,$request->input('options.notes') ?: ''))->onQueue('email')); //@todo Get email from DB.
return view('order_received',['o'=>$so]);
}
-}
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php
index 3189fd0..4897e44 100644
--- a/app/Http/Controllers/SearchController.php
+++ b/app/Http/Controllers/SearchController.php
@@ -66,7 +66,7 @@ class SearchController extends Controller
}
# Look for an ADSL/NBN Service
- foreach (Adsl::Search($request->input('term'))
+ foreach (Service\Broadband::Search($request->input('term'))
->whereIN('account_id',$accounts)
->orderBy('service_number')
->limit(10)->get() as $o)
diff --git a/app/Interfaces/IDs.php b/app/Interfaces/IDs.php
index cd7dbe9..dfdf138 100644
--- a/app/Interfaces/IDs.php
+++ b/app/Interfaces/IDs.php
@@ -9,12 +9,12 @@ interface IDs
*
* @return mixed
*/
- public function getLIDattribute(): string;
+ public function getLIDAttribute(): string;
/**
* Return the system ID of the item
*
* @return mixed
*/
- public function getSIDattribute(): string;
+ public function getSIDAttribute(): string;
}
\ No newline at end of file
diff --git a/app/Interfaces/ProductItem.php b/app/Interfaces/ProductItem.php
new file mode 100644
index 0000000..f5c54fc
--- /dev/null
+++ b/app/Interfaces/ProductItem.php
@@ -0,0 +1,76 @@
+belongsToMany(External\Integrations::class,'external_account',NULL,'external_integration_id');
}
+ public function group()
+ {
+ return $this->hasOneThrough(Group::class,AccountGroup::class,'account_id','id','id','group_id');
+ }
+
public function invoices()
{
return $this->hasMany(Invoice::class);
@@ -89,6 +91,11 @@ class Account extends Model implements IDs
return $active ? $query->active() : $query;
}
+ public function taxes()
+ {
+ return $this->hasMany(Tax::class,'country_id','country_id');
+ }
+
public function user()
{
return $this->belongsTo(User::class);
diff --git a/app/Models/AccountGroup.php b/app/Models/AccountGroup.php
new file mode 100644
index 0000000..e47c74b
--- /dev/null
+++ b/app/Models/AccountGroup.php
@@ -0,0 +1,11 @@
+[
- 'request'=>'options.address',
- 'key'=>'service_address',
- 'validation'=>'required|string:10|unique:ab_service__adsl,service_address',
- 'validation_message'=>'Address is a required field.',
- ],
- 'options.notes'=>[
- 'request'=>'options.notes',
- 'key'=>'order_info.notes',
- 'validation'=>'present',
- 'validation_message'=>'Special Instructions here.',
- ],
- ];
-
- protected $order_model = Service\Adsl::class;
-
- public function product()
- {
- return $this->hasOne(AdslSupplierPlan::class,'id','adsl_supplier_plan_id');
- }
-}
\ No newline at end of file
diff --git a/app/Models/AdslSupplier.php b/app/Models/AdslSupplier.php
index 91e0780..cd246ed 100644
--- a/app/Models/AdslSupplier.php
+++ b/app/Models/AdslSupplier.php
@@ -7,6 +7,9 @@ use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
+/**
+ * @deprecated
+ */
class AdslSupplier extends Model
{
protected $table = 'ab_adsl_supplier';
diff --git a/app/Models/Base/ProductType.php b/app/Models/Base/ProductType.php
index d66057c..09a2b63 100644
--- a/app/Models/Base/ProductType.php
+++ b/app/Models/Base/ProductType.php
@@ -4,11 +4,9 @@ namespace App\Models\Base;
use Illuminate\Database\Eloquent\Model;
-use App\Models\Product;
-
-//@todo column prod_plugin_file should no longer be required
+/**
+ * @deprecated
+ */
abstract class ProductType extends Model
{
- public $timestamps = FALSE;
- public $dateFormat = 'U';
}
\ No newline at end of file
diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php
index 2070e00..3d83208 100644
--- a/app/Models/Invoice.php
+++ b/app/Models/Invoice.php
@@ -45,6 +45,46 @@ class Invoice extends Model implements IDs
protected $dates = ['date_orig','due_date'];
public $dateFormat = 'U';
+ /* Our available billing periods */
+ public const billing_periods = [
+ 0 => [
+ 'name' => 'Weekly',
+ 'interval' => 0.25,
+ ],
+ 1 => [
+ 'name' => 'Monthly',
+ 'interval' => 1,
+ ],
+ 2 => [
+ 'name' => 'Quarterly',
+ 'interval' => 3,
+ ],
+ 3 => [
+ 'name' => 'Semi-Annually',
+ 'interval' => 6,
+ ],
+ 4 => [
+ 'name' => 'Annually',
+ 'interval' => 12,
+ ],
+ 5 => [
+ 'name' => 'Two years',
+ 'interval' => 24,
+ ],
+ 6 => [
+ 'name' => 'Three Years',
+ 'interval' => 36,
+ ],
+ 7 => [
+ 'name' => 'Four Years',
+ 'interval' => 48,
+ ],
+ 8 => [
+ 'name' => 'Five Years',
+ 'interval' => 60,
+ ],
+ ];
+
// Array of items that can be updated with PushNew
protected $pushable = ['items'];
@@ -61,6 +101,58 @@ class Invoice extends Model implements IDs
private int $_total = 0;
private int $_total_tax = 0;
+ /* STATIC */
+
+ /**
+ * This works out what multiplier to use to change billing periods
+ *
+ * @param int $source
+ * @param int $target
+ * @return float
+ */
+ public static function billing_change(int $source,int $target): float
+ {
+ return Arr::get(self::billing_periods,$target.'.interval')/Arr::get(self::billing_periods,$source.'.interval');
+ }
+
+ /**
+ * Return the name for the billing interval
+ *
+ * @param int $interval
+ * @return string
+ */
+ public static function billing_name(int $interval): string
+ {
+ $interval = collect(self::billing_periods)->get($interval);
+
+ return Arr::get($interval,'name','Unknown');
+ }
+
+ /**
+ * Return the number of months in the billing interval
+ *
+ * @param int $interval
+ * @return int
+ */
+ public static function billing_period(int $interval): int
+ {
+ $interval = collect(self::billing_periods)->get($interval);
+
+ return Arr::get($interval,'interval',0);
+ }
+
+ /**
+ * Given a contract in months, this will calculate the number of billing intervals required
+ *
+ * @param int $contract_term
+ * @param int $source
+ * @return int
+ */
+ public static function billing_term(int $contract_term,int $source): int
+ {
+ return ceil(($contract_term ?: 1)/(Arr::get(self::billing_periods,$source.'.interval') ?: 1));
+ }
+
/* RELATIONS */
public function account()
@@ -312,7 +404,7 @@ class Invoice extends Model implements IDs
$lo = $this->account->user->language;
return $return->sortBy(function ($item) use ($lo) {
- return $item->name($lo);
+ return $item->name;
});
}
diff --git a/app/Models/PlanVoip.php b/app/Models/PlanVoip.php
deleted file mode 100644
index f5fc54e..0000000
--- a/app/Models/PlanVoip.php
+++ /dev/null
@@ -1,41 +0,0 @@
-[
- 'request'=>'options.phonenumber',
- 'key'=>'service_number',
- 'validation'=>'nullable|size:10|unique:ab_service__voip,service_number',
- 'validation_message'=>'Phone Number is a required field.',
- ],
- 'options.supplier'=>[
- 'request'=>'options.supplier',
- 'key'=>'order_info.supplier',
- 'validation'=>'required_with:options.phonenumber',
- 'validation_message'=>'Phone Supplier is a required field.',
- ],
- 'options.supplieraccnum'=>[
- 'request'=>'options.supplieraccnum',
- 'key'=>'order_info.supplieraccnum',
- 'validation'=>'required_with:options.phonenumber',
- 'validation_message'=>'Phone Supplier Account Number is a required field.',
- ],
- 'options.notes'=>[
- 'request'=>'options.notes',
- 'key'=>'order_info.notes',
- 'validation'=>'required_if:options.phonenumber,null',
- 'validation_message'=>'Special Instructions here.',
- ],
- ];
-
- protected $order_model = Service\Voip::class;
-}
\ No newline at end of file
diff --git a/app/Models/Product.php b/app/Models/Product.php
index 29996fe..e124f32 100644
--- a/app/Models/Product.php
+++ b/app/Models/Product.php
@@ -5,48 +5,84 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
-use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
+use Leenooks\Traits\ScopeActive;
use App\Interfaces\IDs;
-use App\Traits\NextKey;
+use App\Traits\{ProductDetails,SiteID};
/**
* Class Product
- * Products that are available to sale, and appear on invoices
+ * Products that are available to sale, and appear on invoices.
+ *
+ * Products have one Type (Product/*), made of an Offering (Supplier/*) from a Supplier.
+ * Conversely, Suppliers provide Offerings (Supplier/*) which belong to a Type (Product/*) of a Product.
*
* Attributes for products:
* + lid : Local ID for product (part number)
+ * + supplied : Supplier product provided for this offering
+ * + supplier : Supplier for this offering
+ * + name : Brief Name for our product
+ * + name_short : Product ID for our Product
+ * + name_long : Long Name for our product
+ * + billing_interval : Default Billing Interval
+ * + billing_interval_string: Default Billing Interval in human-readable form
+ * + setup_charge : Charge to setup this product
+ * + setup_charge_taxable : Charge to setup this product including taxes
+ * + base_charge : Default billing amount
+ * + 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
*
+ * Attributes for product types (type - Product/*)
+ * + name : Short Name for our Product
+ * + name_long : Long Name for our Product
+ * + description : Description of offering (Broadband=speed)
+ *
+ * Attributes for supplier's offerings (type->supplied - Supplier/*)
+ * + name : Short Name for suppliers offering
+ * + name_long : Long Name for suppliers offering
+ * + description : Description of offering (Broadband=speed)
+ *
+ * Product Pricing self::pricing is an array of:
+ * [
+ * timeperiod => [
+ * show => true|false (show this time period to the user for ordering)
+ * group => [ pricing/setup ]
+ * ]
+ * ]
+ *
+ * @todo doesnt appear that price_type is used - but could be used to have different offering types billed differently
* @package App\Models
*/
class Product extends Model implements IDs
{
- use HasFactory,NextKey;
-
- const RECORD_ID = 'product';
- public $incrementing = FALSE;
-
- const CREATED_AT = 'date_orig';
- const UPDATED_AT = 'date_last';
-
- public $dateFormat = 'U';
- protected $table = 'ab_product';
+ use HasFactory,SiteID,ProductDetails,ScopeActive;
protected $casts = [
- // @todo convert existing data to a json array
- // 'price_group'=>'array',
+ 'pricing'=>'collection',
];
- protected $with = ['descriptions'];
-
/* RELATIONS */
- public function descriptions()
+ /**
+ * Get the product name in the users language, and if the user isnt logged in, the sites language
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasOne
+ */
+ public function description()
{
- return $this->hasMany(ProductTranslate::class);
+ return $this->hasOne(ProductTranslate::class)
+ ->where('language_id',(Auth::user() && Auth::user()->language_id) ? Auth::user()->language_id : config('site')->language_id);
}
+ /**
+ * Which services are configured with this product
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
public function services()
{
return $this->hasMany(Service::class);
@@ -59,144 +95,203 @@ class Product extends Model implements IDs
*/
public function type()
{
- return $this->morphTo(null,'model','prod_plugin_data');
+ return $this->morphTo(null,'model','model_id');
+ }
+
+ /* INTERFACES */
+
+ public function getLIDAttribute(): string
+ {
+ return sprintf('%04s',$this->id);
+ }
+
+ public function getSIDAttribute(): string
+ {
+ return sprintf('%02s-%s',$this->site_id,$this->getLIDattribute());
}
/* ATTRIBUTES */
/**
- * Get the service category (from the product)
+ * The amount we invoice each time period for this service
*
- * @return string
+ * @param int|NULL $timeperiod
+ * @param Group|NULL $go
+ * @return float
*/
- public function getCategoryAttribute()
+ public function getBaseChargeAttribute(int $timeperiod=NULL,Group $go=NULL): float
{
- return $this->prod_plugin_file ?: 'Other';
- }
-
- public function getContractTermAttribute()
- {
- switch ($this->prod_plugin_file) {
- case 'ADSL': return $this->plugin()->contract_term;
- // @todo Incorporate into DB
- case 'VOIP': return 12;
-
- // @todo Change this after contracts implemented.
- default:
- return 'TBA';
- }
- }
-
- public function getDefaultBillingAttribute()
- {
- return Arr::get($this->PricePeriods(),$this->price_recurr_default);
- }
-
- public function getDefaultCostAttribute()
- {
- // @todo Integrate this into a Tax::class
- return Arr::get($this->price_array,sprintf('%s.1.price_base',$this->price_recurr_default))*1.1;
- }
-
- private function getDefaultLanguage()
- {
- return config('site')->language;
- }
-
- public function getDescriptionAttribute()
- {
- // @todo If the user has selected a specific language.
- return $this->description($this->getDefaultLanguage());
+ return $this->getCharge('base',$timeperiod,$go);
}
/**
- * Product Local ID
+ * The amount we invoice each time period for this service, including taxes
*
- * @return string
+ * @param int|null $timeperiod
+ * @param Group|null $go
+ * @param Collection|NULL $taxes
+ * @return float
*/
- public function getLIDattribute(): string
+ public function getBaseChargeTaxableAttribute(int $timeperiod=NULL,Group $go=NULL,Collection $taxes=NULL): float
{
- return sprintf('%04s',$this->id);
- }
-
- public function getMinimumCostAttribute()
- {
- $table = [
- 0=>4,
- 1=>1,
- 2=>1/3,
- 3=>1/6,
- 4=>1/12,
- 5=>1/24,
- 6=>1/36,
- 7=>1/48,
- 8=>1/60,
- ];
-
- return $this->setup_cost + ( $this->default_cost * Arr::get($table,$this->price_recurr_default) * $this->contract_term);
- }
-
- public function getNameAttribute(Language $lo=NULL)
- {
- if (is_null($lo))
- $lo = $this->getDefaultLanguage();
-
- return $this->descriptions->where('language_id',$lo->id)->first()->description_short;
- }
-
- public function getNameShortAttribute(Language $lo=NULL)
- {
- if (is_null($lo))
- $lo = $this->getDefaultLanguage();
-
- return $this->descriptions->where('language_id',$lo->id)->first()->name;
- }
-
- public function getProductTypeAttribute()
- {
- return $this->plugin()->product->name;
- }
-
- public function getPriceArrayAttribute()
- {
- 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()
- {
- $table = [
- 0=>_('One-time Charge'),
- 1=>_('Recurring Membership/Subscription'),
- 2=>_('Trial for Membership/Subscription'),
- ];
- }
-
- public function getProductIdAttribute()
- {
- return sprintf('#%04s',$this->id);
- }
-
- public function getSetupCostAttribute()
- {
- // @todo Integrate this into a Tax::class
- return Arr::get($this->price_array,sprintf('%s.1.price_setup',$this->price_recurr_default))*1.1;
+ return Tax::tax_calc($this->getBaseChargeAttribute($timeperiod,$go),$taxes ?: config('site')->taxes);
}
/**
- * Product System ID
+ * The base cost of this product at the appropriate billing interval
+ *
+ * @return float
+ */
+ public function getBaseCostAttribute(): float
+ {
+ return round($this->type->supplied->base_cost*Invoice::billing_change($this->type->supplied->getBillingIntervalAttribute(),$this->getBillingIntervalAttribute()) ?: 0,2);
+ }
+
+ /**
+ * The base cost of this product at the appropriate billing interval including taxes
+ *
+ * @param Collection|NULL $taxes
+ * @return float
+ */
+ public function getBaseCostTaxableAttribute(Collection $taxes=NULL): float
+ {
+ return Tax::tax_calc($this->getBaseCostAttribute(),$taxes ?: config('site')->taxes);;
+ }
+
+ /**
+ * Our default billing interval
+ * Its the max of what we define, or what the supplier bills us at
+ *
+ * @return int
+ */
+ public function getBillingIntervalAttribute(): int
+ {
+ return max($this->price_recur_default,$this->type->supplied->getBillingIntervalAttribute());
+ }
+
+ /**
+ * How long must this product be purchased for as a service.
+ *
+ * @return int
+ */
+ public function getContractTermAttribute(): int
+ {
+ return $this->type->getContractTermAttribute();
+ }
+
+ /**
+ * Get the minimum cost of this product
+ *
+ * @param int|null $timeperiod
+ * @param Group|null $go
+ * @return float
+ */
+ public function getMinChargeAttribute(int $timeperiod=NULL,Group $go=NULL): float
+ {
+ return $this->getSetupChargeAttribute($timeperiod,$go)+$this->getBaseChargeAttribute($timeperiod,$go)*Invoice::billing_term($this->getContractTermAttribute(),$this->getBillingIntervalAttribute());
+ }
+
+ /**
+ * Get the minimum cost of this product with taxes
+ *
+ * @param int|null $timeperiod
+ * @param Group|null $go
+ * @param Collection|NULL $taxes
+ * @return float
+ */
+ public function getMinChargeTaxableAttribute(int $timeperiod=NULL,Group $go=NULL,Collection $taxes=NULL): float
+ {
+ return Tax::tax_calc($this->getMinChargeAttribute($timeperiod,$go),$taxes ?: config('site')->taxes);
+ }
+
+ /**
+ * Our products short descriptive name
*
* @return string
*/
- public function getSIDattribute(): string
+ public function getNameAttribute(): string
{
- return sprintf('%02s-%s',$this->site_id,$this->getLIDattribute());
+ return $this->description ? $this->description->description_short : 'Unknown PRODUCT';
}
+ /**
+ * Our products PID
+ *
+ * @return string
+ */
+ public function getNameShortAttribute(): string
+ {
+ return $this->description ? $this->description->name : 'Unknown PID';
+ }
+
+ /**
+ * This product full description
+ *
+ * @return string
+ */
+ public function getNameLongAttribute(): string
+ {
+ return $this->description->description_full;
+ }
+
+ /**
+ * Get our product type
+ *
+ * @return string
+ */
+ public function getProductTypeAttribute(): string
+ {
+ return ($this->type && $this->type->supplied) ? $this->type->supplied->getTypeAttribute() : 'Unknown';
+ }
+
+ /**
+ * The charge to setup this service
+ *
+ * @param int|null $timeperiod
+ * @param Group|null $go
+ * @return float
+ */
+ public function getSetupChargeAttribute(int $timeperiod=NULL,Group $go=NULL): float
+ {
+ return $this->getCharge('setup',$timeperiod,$go);
+ }
+
+ /**
+ * The charge to setup this service including taxes
+ *
+ * @param int|null $timeperiod
+ * @param Group|null $go
+ * @param Collection|null $taxes
+ * @return float
+ */
+ public function getSetupChargeTaxableAttribute(int $timeperiod=NULL,Group $go=NULL,Collection $taxes=NULL): float
+ {
+ return Tax::tax_calc($this->getSetupChargeAttribute($timeperiod,$go),$taxes ?: config('site')->taxes);
+ }
+
+ /**
+ * The charge to setup this service
+ *
+ * @return float
+ */
+ public function getSetupCostAttribute(): float
+ {
+ return $this->type->supplied->setup_cost ?: 0;
+ }
+
+ /**
+ * The charge to setup this service
+ *
+ * @param Collection|null $taxes
+ * @return float
+ */
+ public function getSetupCostTaxableAttribute(Collection $taxes=NULL): float
+ {
+ return Tax::tax_calc($this->getSetupCostAttribute(),$taxes ?: config('site')->taxes);;
+ }
+
+ /* METHODS */
+
/**
* Return if this product captures usage data
*
@@ -204,81 +299,49 @@ class Product extends Model implements IDs
*/
public function hasUsage(): bool
{
- // @todo This should be configured in the DB
- return in_array($this->model, ['App\Models\Product\Adsl']);
- }
-
- public function scopeActive()
- {
- return $this->where('active',TRUE);
- }
-
- public function description(Language $lo=NULL)
- {
- if (is_null($lo))
- $lo = $this->getDefaultLanguage();
-
- return $this->descriptions->where('language_id',$lo->id)->first()->description_full;
- }
-
- public function orderValidation(Request $request)
- {
- return $this->plugin()->orderValidation($request);
- }
-
- private function plugin()
- {
- switch ($this->prod_plugin_file) {
- case 'ADSL':
- return AdslPlan::findOrFail($this->prod_plugin_data);
- case 'VOIP':
- return new PlanVoip;
- }
+ return $this->type->supplied->hasUsage();
}
/**
- * Get the price for this product based on the period being requested.
+ * Get a charge value from the pricing array
*
- * If the price period doesnt exist, we'll take the default period (0) which should.
+ * @param string $type
+ * @param int|NULL $timeperiod
+ * @param Group|NULL $go
+ * @return float
+ */
+ private function getCharge(string $type,int $timeperiod=NULL,Group $go=NULL): float
+ {
+ static $default = NULL;
+ if (! $go) {
+ if (is_null($default))
+ $default = Group::findOrFail(0); // All public users
+
+ $go = $default;
+ }
+
+ if (is_null($timeperiod))
+ $timeperiod = $this->getBillingIntervalAttribute();
+
+ // If the price doesnt exist for $go->id, use $go->id = 0 which is all users.
+ if (! $price=Arr::get($this->pricing,sprintf('%d.%d.%s',$timeperiod,$go->id,$type)))
+ $price = Arr::get($this->pricing,sprintf('%d.%d.%s',$timeperiod,0,$type));
+
+ // @todo - if price doesnt exist for the time period, reduce down to timeperiod 1 and multiply appropriately.
+ if (is_null($price))
+ abort(500,sprintf('Price is NULL, we need to find it timeperiod[%s] group[%s]',$timeperiod,$go->id));
+
+ return round($price,2);
+ }
+
+ /**
+ * When receiving an order, validate that we have all the required information for the product type
*
- * @param int $period
+ * @param Request $request
* @return mixed
*/
- public function price(int $period,string $key='price_base')
+ public function orderValidation(Request $request): ?Model
{
- return Arr::get(
- $this->price_array,
- sprintf('%s.1.%s',$period,$key),
- Arr::get($this->price_array,sprintf('%s.0.%s',$period,$key))
- );
- }
-
- public function PricePeriods()
- {
- return [
- 0=>_('Weekly'),
- 1=>_('Monthly'),
- 2=>_('Quarterly'),
- 3=>_('Semi-Annually'),
- 4=>_('Annually'),
- 5=>_('Two years'),
- 6=>_('Three Years'),
- 7=>_('Four Years'),
- 8=>_('Five Years'),
- ];
- }
-
- /**
- * Get the product name
- *
- * @param Language $lo
- * @return string Product Name
- */
- public function name(Language $lo=NULL)
- {
- if (is_null($lo))
- $lo = $this->getDefaultLanguage();
-
- return $this->descriptions->where('language_id',$lo->id)->first()->name;
+ return $this->type->orderValidation($request);
}
}
\ No newline at end of file
diff --git a/app/Models/Product/Adsl.php b/app/Models/Product/Adsl.php
deleted file mode 100644
index 44bc7fb..0000000
--- a/app/Models/Product/Adsl.php
+++ /dev/null
@@ -1,159 +0,0 @@
-'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 'speed':
- return $this->product->speed;
- }
-
- // 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;
- }
-
- public function getSupplierAttribute()
- {
- return $this->getRelationValue('supplier');
- }
-}
\ No newline at end of file
diff --git a/app/Models/Product/Broadband.php b/app/Models/Product/Broadband.php
new file mode 100644
index 0000000..ddf48a8
--- /dev/null
+++ b/app/Models/Product/Broadband.php
@@ -0,0 +1,178 @@
+[
+ 'request'=>'options.address',
+ 'key'=>'service_address',
+ 'validation'=>'required|string:10|unique:ab_service__adsl,service_address',
+ 'validation_message'=>'Address is a required field.',
+ ],
+ 'options.notes'=>[
+ 'request'=>'options.notes',
+ 'key'=>'order_info.notes',
+ 'validation'=>'present',
+ 'validation_message'=>'Special Instructions here.',
+ ],
+ ];
+
+ protected string $order_model = ServiceBroadband::class;
+
+ /* RELATIONS */
+
+ /**
+ * The product that sells this type
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\MorphOne
+ */
+ public function product()
+ {
+ return $this->morphOne(Product::class, null,'model','model_id');
+ }
+
+ /**
+ * The offering supplied with this product
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasOne
+ */
+ public function supplied()
+ {
+ 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 */
+
+ /**
+ * 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();
+
+ foreach (array_keys(Supplier\Broadband::traffic_map) as $k => $v) {
+ // Base Config
+ $config->put($k,$this->{$k});
+ // Excess Config
+ $config->put($v,$this->{$v});
+ }
+
+ // 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->supplied->allowance($config,$data,$ceil);
+ }
+
+ /* ATTRIBUTES */
+
+ /**
+ * Return the suppliers cost for this service
+ *
+ * @return float
+ */
+ // @todo To check
+ public function allowance_cost(): float
+ {
+ $result = 0;
+ foreach ($this->supplied->allowance(NULL,$this->allowance([])->toArray()) as $k=>$v) {
+ $result += -$v*$this->supplied->{Supplier\Broadband::traffic_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;
+ }
+
+ /**
+ * The product contract term is the highest of
+ * + This defined contract_term
+ * + The suppliers contract_term
+ *
+ * @return int
+ */
+ public function getContractTermAttribute(): int
+ {
+ return max($this->attributes['contract_term'],$this->supplied->getContractTermAttribute());
+ }
+
+ public function getCostAttribute(): float
+ {
+ abort(500,'deprecated');
+ // @todo Tax shouldnt be hard coded
+ return ($this->supplied->base_cost+$this->allowance_cost())*1.1;
+ }
+
+ public function getSupplierAttribute()
+ {
+ abort(500,'deprecated');
+ return $this->getRelationValue('supplier');
+ }
+
+ public function hasUsage(): bool
+ {
+ return TRUE;
+ }
+}
\ No newline at end of file
diff --git a/app/Models/Product/Domain.php b/app/Models/Product/Domain.php
index d0b0ec8..348d59e 100644
--- a/app/Models/Product/Domain.php
+++ b/app/Models/Product/Domain.php
@@ -25,6 +25,11 @@ class Domain extends ProductType implements ProductSupplier
return '';
}
+ public function getContractTermAttribute(): int
+ {
+ return 12;
+ }
+
public function getCostAttribute(): float
{
// N/A
@@ -35,4 +40,14 @@ class Domain extends ProductType implements ProductSupplier
{
return '';
}
+
+ public function getTypeAttribute()
+ {
+ return 'Domain Name';
+ }
+
+ public function hasUsage(): bool
+ {
+ return FALSE;
+ }
}
\ No newline at end of file
diff --git a/app/Models/Product/Generic.php b/app/Models/Product/Generic.php
index 81b1797..6d81e20 100644
--- a/app/Models/Product/Generic.php
+++ b/app/Models/Product/Generic.php
@@ -6,4 +6,18 @@ use App\Models\Base\ProductType;
class Generic extends ProductType
{
+ public function getContractTermAttribute(): int
+ {
+ return 0;
+ }
+
+ public function getTypeAttribute()
+ {
+ return 'Generic';
+ }
+
+ public function hasUsage(): bool
+ {
+ return FALSE;
+ }
}
diff --git a/app/Models/Product/Host.php b/app/Models/Product/Host.php
index cdd961a..e9f23d2 100644
--- a/app/Models/Product/Host.php
+++ b/app/Models/Product/Host.php
@@ -9,4 +9,19 @@ class Host extends ProductType
{
use NextKey;
const RECORD_ID = '';
+
+ public function getContractTermAttribute(): int
+ {
+ return 12;
+ }
+
+ public function getTypeAttribute()
+ {
+ return 'Hosting';
+ }
+
+ public function hasUsage(): bool
+ {
+ return FALSE;
+ }
}
\ No newline at end of file
diff --git a/app/Models/Product/SSL.php b/app/Models/Product/SSL.php
index 874e980..0896bbb 100644
--- a/app/Models/Product/SSL.php
+++ b/app/Models/Product/SSL.php
@@ -27,6 +27,11 @@ class SSL extends ProductType implements ProductSupplier
return '';
}
+ public function getContractTermAttribute(): int
+ {
+ return 12;
+ }
+
public function getCostAttribute(): float
{
// N/A
@@ -51,4 +56,14 @@ class SSL extends ProductType implements ProductSupplier
return $o;
}
+
+ public function getTypeAttribute()
+ {
+ return 'SSL Certificate';
+ }
+
+ public function hasUsage(): bool
+ {
+ return FALSE;
+ }
}
\ No newline at end of file
diff --git a/app/Models/Product/Voip.php b/app/Models/Product/Voip.php
index f500361..e271dc5 100644
--- a/app/Models/Product/Voip.php
+++ b/app/Models/Product/Voip.php
@@ -4,9 +4,56 @@ namespace App\Models\Product;
use App\Models\Base\ProductType;
use App\Traits\NextKey;
+use App\Traits\OrderServiceOptions;
class Voip extends ProductType
{
use NextKey;
const RECORD_ID = '';
+
+ use OrderServiceOptions;
+
+ protected $order_attributes = [
+ 'options.phonenumber'=>[
+ 'request'=>'options.phonenumber',
+ 'key'=>'service_number',
+ 'validation'=>'nullable|size:10|unique:ab_service__voip,service_number',
+ 'validation_message'=>'Phone Number is a required field.',
+ ],
+ 'options.supplier'=>[
+ 'request'=>'options.supplier',
+ 'key'=>'order_info.supplier',
+ 'validation'=>'required_with:options.phonenumber',
+ 'validation_message'=>'Phone Supplier is a required field.',
+ ],
+ 'options.supplieraccnum'=>[
+ 'request'=>'options.supplieraccnum',
+ 'key'=>'order_info.supplieraccnum',
+ 'validation'=>'required_with:options.phonenumber',
+ 'validation_message'=>'Phone Supplier Account Number is a required field.',
+ ],
+ 'options.notes'=>[
+ 'request'=>'options.notes',
+ 'key'=>'order_info.notes',
+ 'validation'=>'required_if:options.phonenumber,null',
+ 'validation_message'=>'Special Instructions here.',
+ ],
+ ];
+
+ protected $order_model = \App\Models\Service\Voip::class;
+
+ public function getContractTermAttribute(): int
+ {
+ return 12;
+ }
+
+ public function getTypeAttribute()
+ {
+ return 'VOIP';
+ }
+
+ public function hasUsage(): bool
+ {
+ return TRUE;
+ }
}
\ No newline at end of file
diff --git a/app/Models/Service.php b/app/Models/Service.php
index d97f297..9405f0a 100644
--- a/app/Models/Service.php
+++ b/app/Models/Service.php
@@ -18,14 +18,15 @@ use Leenooks\Carbon;
use Symfony\Component\HttpKernel\Exception\HttpException;
use App\Interfaces\IDs;
-use App\Traits\NextKey;
/**
* Class Service
* Services that belong to an account
*
* Attributes for services:
- * + billing_period : The period that this service is billed for by default
+ * + 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
* + 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
@@ -33,9 +34,10 @@ use App\Traits\NextKey;
*
* @package App\Models
*/
+// @todo All the methods/attributes in this file need to be checked.
class Service extends Model implements IDs
{
- use NextKey,HasFactory;
+ use HasFactory;
const RECORD_ID = 'service';
public $incrementing = FALSE;
@@ -85,6 +87,7 @@ class Service extends Model implements IDs
'status',
];
+ /*
protected $with = [
'account.language',
'charges',
@@ -92,6 +95,7 @@ class Service extends Model implements IDs
'product',
'type',
];
+ */
// @todo Change to self::INACTIVE_STATUS
private $inactive_status = [
@@ -370,7 +374,6 @@ class Service extends Model implements IDs
* Product of the service
*
* @return BelongsTo
- * @deprecated use type->product
*/
public function product()
{
@@ -491,10 +494,10 @@ class Service extends Model implements IDs
public function getBillingPriceAttribute(): float
{
// @todo Temporary for services that dont have recur_schedule set.
- if (is_null($this->recur_schedule) OR is_null($this->product->price($this->recur_schedule)))
+ if (is_null($this->recur_schedule) OR is_null($this->product->getBaseChargeAttribute($this->recur_schedule,$this->account->group)))
$this->price=0;
- return $this->addTax(is_null($this->price) ? $this->product->price($this->recur_schedule) : $this->price);
+ return $this->addTax(is_null($this->price) ? $this->product->getBaseChargeAttribute($this->recur_schedule,$this->account->group) : $this->price);
}
public function getBillingMonthlyPriceAttribute(): float
@@ -516,11 +519,32 @@ class Service extends Model implements IDs
/**
* Return the service billing period
*
+ * @return int
+ */
+ public function getBillingIntervalAttribute(): int
+ {
+ return $this->recur_schedule ?: $this->product->getBillingIntervalAttribute();
+ }
+
+ /**
+ * Return a human friendly name for the billing interval
+ *
* @return string
*/
- public function getBillingPeriodAttribute(): string
+ public function getBillingIntervalStringAttribute(): string
{
- return Arr::get($this->product->PricePeriods(),$this->recur_schedule,'Unknown');
+ return Invoice::billing_name($this->getBillingIntervalAttribute());
+ }
+
+ /**
+ * This function will determine the minimum contract term for a service, which is the maximum of
+ * this::type->contract_term, or the product->type->contract_term();
+ *
+ * @return int
+ */
+ public function getContractTermAttribute(): int
+ {
+ abort(500,'To implement (Dec 2021)');
}
/**
@@ -561,42 +585,42 @@ class Service extends Model implements IDs
{
switch ($this->recur_schedule) {
// Weekly
- case 0: $date = $this->product->price_recurr_strict
+ case 0: $date = $this->product->price_recur_strict
? $this->getInvoiceNextAttribute()->endOfWeek()
: $this->getInvoiceNextAttribute()->addWeek()->subDay();
break;
// Monthly
case 1:
- $date = $this->product->price_recurr_strict
+ $date = $this->product->price_recur_strict
? $this->getInvoiceNextAttribute()->endOfMonth()
: $this->getInvoiceNextAttribute()->addMonth()->subDay();
break;
// Quarterly
case 2:
- $date = $this->product->price_recurr_strict
+ $date = $this->product->price_recur_strict
? $this->getInvoiceNextAttribute()->endOfQuarter()
: $this->getInvoiceNextAttribute()->addQuarter()->subDay();
break;
// Half Yearly
case 3:
- $date = $this->product->price_recurr_strict
+ $date = $this->product->price_recur_strict
? $this->getInvoiceNextAttribute()->endOfHalf()
: $this->getInvoiceNextAttribute()->addQuarter(2)->subDay();
break;
// Yearly
case 4:
- $date = $this->product->price_recurr_strict
+ $date = $this->product->price_recur_strict
? $this->getInvoiceNextAttribute()->endOfYear()
: $this->getInvoiceNextAttribute()->addYear()->subDay();
break;
// Two Yearly
case 5:
- if (!$this->product->price_recurr_strict)
+ if (!$this->product->price_recur_strict)
$date = $this->getInvoiceNextAttribute()->addYear(2)->subDay();
else {
$date = $this->getInvoiceNextAttribute()->addYear(2)->subDay()->endOfYear();
@@ -608,7 +632,7 @@ class Service extends Model implements IDs
break;
// Three Yearly
- // NOTE: price_recurr_strict ignored
+ // NOTE: price_recur_strict ignored
case 6: $date = $this->getInvoiceNextAttribute()->addYear(3)->subDay(); break;
default: throw new Exception('Unknown recur_schedule');
@@ -624,7 +648,7 @@ class Service extends Model implements IDs
public function getInvoiceNextQuantityAttribute()
{
// If we are not rounding to the first day of the cycle, then it is always a full cycle
- if (! $this->product->price_recurr_strict)
+ if (! $this->product->price_recur_strict)
return 1;
$n = $this->invoice_next->diff($this->invoice_next_end)->days+1;
@@ -759,11 +783,12 @@ class Service extends Model implements IDs
/**
* Get the Product's Category for this service
*
- * @deprecated use product->category directly
+ * @deprecated use product->getProductTypeAttribute() directly
*/
public function getProductCategoryAttribute(): string
{
- return $this->product->category;
+ abort(500,'deprecated');
+ return $this->product->getProductTypeAttribute();
}
/**
@@ -774,6 +799,7 @@ class Service extends Model implements IDs
*/
public function getProductNameAttribute(): string
{
+ abort(500,'deprecated');
return $this->product->name($this->account->language);
}
@@ -860,7 +886,7 @@ class Service extends Model implements IDs
public function getSTypeAttribute(): string
{
switch($this->product->model) {
- case 'App\Models\Product\Adsl': return 'broadband';
+ case 'App\Models\Product\Broadband': return 'broadband';
default: return $this->type->type;
}
}
@@ -918,6 +944,18 @@ class Service extends Model implements IDs
: $this->status;
}
+
+ /**
+ * Return the type of service is provided.
+ *
+ * @return string
+ */
+ public function getServiceTypeAttribute(): string
+ {
+ // @todo This is temporary, while we clean the database.
+ return ($this->product->type && $this->product->type->supplied) ? $this->product->type->supplied->getTypeAttribute() : '** TBA **';
+ }
+
/**
* URL used by an admin to administer the record
*
@@ -1275,7 +1313,7 @@ class Service extends Model implements IDs
// Connection charges are only charged once
if ((! $this->invoice_items->filter(function($item) { return $item->item_type==4; })->sum('total'))
AND ($this->isPending() OR is_null($this->invoice_to))
- AND $this->product->price($this->recur_schedule,'price_setup'))
+ AND $this->product->getSetupChargeAttribute($this->recur_schedule,$this->account->group))
{
$o = new InvoiceItem;
@@ -1283,7 +1321,7 @@ class Service extends Model implements IDs
$o->service_id = $this->id;
$o->product_id = $this->product_id;
$o->item_type = 4; // @todo change to const or something
- $o->price_base = $this->product->price($this->recur_schedule,'price_setup'); // @todo change to a method in this class
+ $o->price_base = $this->product->getSetupChargeAttribute($this->recur_schedule,$this->account->group);
//$o->recurring_schedule = $this->recur_schedule;
$o->date_start = $this->invoice_next;
$o->date_stop = $this->invoice_next;
@@ -1309,7 +1347,7 @@ class Service extends Model implements IDs
$o->product_id = $this->product_id;
$o->item_type = 0;
$o->price_base = is_null($this->price)
- ? (is_null($this->price_override) ? $this->product->price($this->recur_schedule) : $this->price_override)
+ ? (is_null($this->price_override) ? $this->product->getBaseChargeAttribute($this->recur_schedule,$this->account->group) : $this->price_override)
: $this->price; // @todo change to a method in this class
$o->recurring_schedule = $this->recur_schedule;
$o->date_start = $this->invoice_next;
@@ -1354,6 +1392,7 @@ class Service extends Model implements IDs
*/
private function ServicePlugin()
{
+ abort(500,'deprecated');
// @todo: All services should be linked to a product. This might require data cleaning for old services not linked to a product.
if (! is_object($this->product))
return NULL;
diff --git a/app/Models/Service/Adsl.php b/app/Models/Service/Broadband.php
similarity index 88%
rename from app/Models/Service/Adsl.php
rename to app/Models/Service/Broadband.php
index 99b7221..06cc33b 100644
--- a/app/Models/Service/Adsl.php
+++ b/app/Models/Service/Broadband.php
@@ -3,17 +3,15 @@
namespace App\Models\Service;
use Illuminate\Support\Collection;
-
-use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Leenooks\Carbon;
use App\Interfaces\{ServiceItem,ServiceUsage};
-use App\Models\AdslSupplierPlan;
use App\Models\Base\ServiceType;
+use App\Models\Supplier\Broadband as SupplierBroadband;
use App\Traits\NextKey;
-class Adsl extends ServiceType implements ServiceItem,ServiceUsage
+class Broadband extends ServiceType implements ServiceItem,ServiceUsage
{
private const LOGKEY = 'MSA';
@@ -26,21 +24,7 @@ class Adsl extends ServiceType implements ServiceItem,ServiceUsage
];
protected $table = 'ab_service__adsl';
- /** RELATIONSHIPS **/
-
- /**
- * The suppliers product
- *
- * @return \Illuminate\Database\Eloquent\Relations\HasOne
- */
- public function product()
- {
- return $this
- ->hasOne(AdslSupplierPlan::class,'id','adsl_supplier_plan_id')
- ->withDefault(function() {
- $o = new AdslSupplierPlan;
- });
- }
+ /* RELATIONS */
/**
* The accounts that this user manages
@@ -53,7 +37,7 @@ class Adsl extends ServiceType implements ServiceItem,ServiceUsage
return $this->hasMany(AdslTraffic::class,'ab_service_adsl_id');
}
- /** SCOPES */
+ /* SCOPES */
/**
* Search for a record
@@ -71,12 +55,13 @@ class Adsl extends ServiceType implements ServiceItem,ServiceUsage
->orWhere('ipaddress','like','%'.$term.'%');
}
- /** ATTRIBUTES **/
-
/**
* @deprecated use $o->service_name;
* @return mixed|string
*/
+
+ /* ATTRIBUTES */
+
public function getNameAttribute()
{
return $this->service_number ?: $this->service_address;
@@ -107,6 +92,8 @@ class Adsl extends ServiceType implements ServiceItem,ServiceUsage
return $this->service_number ?: $this->service_address;
}
+ /* METHODS */
+
/**
* Is this service currently in a contract
*
@@ -117,6 +104,18 @@ class Adsl extends ServiceType implements ServiceItem,ServiceUsage
return $this->service_contract_date AND $this->service_contract_date->addMonths($this->contract_term)->isFuture();
}
+ /**
+ * Return the suppliers offering that this service is providing
+ *
+ * @return SupplierBroadband
+ */
+ public function supplied(): SupplierBroadband
+ {
+ return $this->provided_adsl_plan_id
+ ? SupplierBroadband::findOrFail($this->provided_adsl_plan_id)
+ : $this->service->product->type->supplied;
+ }
+
/**
* Return service usage data
*
diff --git a/app/Models/Service/SSL.php b/app/Models/Service/SSL.php
index 33455ef..232b293 100644
--- a/app/Models/Service/SSL.php
+++ b/app/Models/Service/SSL.php
@@ -67,7 +67,7 @@ class SSL extends ServiceType implements ServiceItem
{
return $this->cert
? Arr::get($this->crt_parse,'subject.CN')
- : Arr::get(openssl_csr_get_subject($this->csr),'CN');
+ : Arr::get(openssl_csr_get_subject($this->csr),'CN','');
}
public function inContract(): bool
diff --git a/app/Models/Site.php b/app/Models/Site.php
index 65b7147..6749972 100644
--- a/app/Models/Site.php
+++ b/app/Models/Site.php
@@ -58,6 +58,11 @@ class Site extends Model
return $this->belongsTo(Language::class);
}
+ public function taxes()
+ {
+ return $this->hasMany(Tax::class,'country_id','country_id');
+ }
+
/* ATTRIBUTES */
/**
diff --git a/app/Models/Supplier.php b/app/Models/Supplier.php
index 6d0d1c7..50c6014 100644
--- a/app/Models/Supplier.php
+++ b/app/Models/Supplier.php
@@ -3,18 +3,93 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Arr;
+use Illuminate\Support\Collection;
use Leenooks\Traits\ScopeActive;
+use App\Models\Supplier\{Broadband,Ethernet,HSPA};
+
class Supplier extends Model
{
use ScopeActive;
public $timestamps = FALSE;
+ /* The offerings we provide */
+ public const offering_types = [
+ 'broadband' => [
+ 'name' => 'Broadband',
+ 'class' => Broadband::class,
+ ],
+ 'hspa' => [
+ 'name' => 'Mobile Broadband',
+ 'class' => HSPA::class,
+ ],
+ 'ethernet' => [
+ 'name' => 'Ethernet Broadband',
+ 'class' => Ethernet::class,
+ ],
+ 'domainname' => [
+ 'name' => 'Domain Name',
+ //'class' => Domain::class,
+ ],
+ 'generic' => [
+ 'name' => 'Generic',
+ //'class' => Generic::class,
+ ],
+ 'hosting' => [
+ 'name' => 'Hosting',
+ //'class' => Host::class,
+ ],
+ 'voip' => [
+ 'name' => 'VOIP Telephone',
+ //'class' => Voip::class,
+ ],
+ ];
+
/* RELATIONS */
public function detail()
{
return $this->hasOne(SupplierDetail::class);
}
+
+ /* 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;
+ }
}
\ No newline at end of file
diff --git a/app/Models/AdslSupplierPlan.php b/app/Models/Supplier/Broadband.php
similarity index 51%
rename from app/Models/AdslSupplierPlan.php
rename to app/Models/Supplier/Broadband.php
index 0367df5..d7e18fa 100644
--- a/app/Models/AdslSupplierPlan.php
+++ b/app/Models/Supplier/Broadband.php
@@ -1,14 +1,112 @@
'datetime:H:i',
+ 'offpeak_end' => 'datetime:H:i',
+ ];
+
+ protected $table = 'supplier_broadband';
+
+ // Map the table fields, with the extra fields
+ public const traffic_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 the NULL relationships - and where traffic gets applied if NULL
+ public const traffic_merge = [
+ 'extra_up_offpeak' => 'base_down_offpeak',
+ 'extra_down_offpeak' => 'base_down_peak',
+ 'extra_up_peak' => 'base_down_peak',
+ 'extra_down_peak' => 'base_down_peak',
+ ];
+
+ /* INTERFACES */
+
+ public function supplier_detail(): BelongsTo
+ {
+ return $this->belongsTo(SupplierDetail::class);
+ }
+
+ public function types(): BelongsToMany
+ {
+ return $this->belongsToMany(ProductBroadband::class,'supplier_broadband','id','id','id','supplier_broadband_id');
+ }
+
+ public function getBaseCostTaxableAttribute(): float
+ {
+ return Tax::tax_calc($this->attributes['base_cost'],config('site')->taxes);
+ }
+
+ public function getBillingIntervalAttribute(): int
+ {
+ return 1; // Monthly
+ }
+
+ /**
+ * This contract term is the highest of
+ * + The defined contract_term
+ * + The default months in a billing interval
+ *
+ * @return int
+ */
+ public function getContractTermAttribute(): int
+ {
+ return max(Invoice::billing_period(self::getBillingIntervalAttribute()),Arr::get($this->attributes,'contract_term'));
+ }
+
+ public function getMinCostAttribute(): float
+ {
+ return $this->attributes['setup_cost']+$this->attributes['base_cost']*Invoice::billing_term($this->getContractTermAttribute(),$this->getBillingIntervalAttribute());
+ }
+
+ public function getMinCostTaxableAttribute(): float
+ {
+ return Tax::tax_calc($this->getMinCostAttribute(),config('site')->taxes);
+ }
+
+ public function getNameAttribute(): string
+ {
+ return $this->product_id ?: 'Supplier PID Unknown';
+ }
+
+ public function getNameLongAttribute(): string
+ {
+ return $this->product_desc ?: 'Supplier NAME Unknown';
+ }
+
+ public function getSetupCostTaxableAttribute(): float
+ {
+ return Tax::tax_calc($this->attributes['setup_cost'],config('site')->taxes);
+ }
+
+ public function getTypeAttribute(): string
+ {
+ return Arr::get(collect(Supplier::offering_types)->firstWhere('class',get_class($this)),'name','Unknown');
+ }
+
+ /* METHODS */
/**
* Determine how traffic is counted for Broadband links.
@@ -17,7 +115,7 @@ class AdslSupplierPlan extends Model
* + down_peak, when not NULL, traffic is included to value of this metric, extra traffic is charged per extra_peak
* + down_offpeak, when not NULL, traffic is included to the value of metric, extra traffic is charged per extra_offpeak
*
- * If:
+ * If:
* + UPLOADS are charged and there are no PEAK/OFFPEAK periods (therefore all
* traffic is charged), the allowance will be shown as 1 metric - TRAFFIC.
* + UPLOADS are charged and there are PEAK/OFFPEAK periods the allowance
@@ -39,29 +137,15 @@ class AdslSupplierPlan extends Model
* + If extra_up_peak is NULL add traffic_up_peak to traffic_down_peak
* + If extra_up_offpeak is NULL add traffic_up_offpeak to traffic_down_offpeak
*
- * @param array $config The configuration of the link, if NULL assume the supplieres configuration
- * @param array $data The traffic used on this link, determine whats left or over
- * @param bool $format @deprecate
- * @param bool $over @deprecate
- * @param bool $ceil Round the numbers to integers
+ * @param Collection|null $config The configuration of the link, if NULL assume the supplieres configuration
+ * @param array $data The traffic used on this link, determine whats left or over
+ * @param bool $ceil Round the numbers to integers
* @return array|string
*/
- public function allowance(Collection $config=NULL,array $data=[],$ceil=TRUE) {
- // Map the table fields, with the extra fields
- $map = collect([
- '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 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',
- ]);
+ public function allowance(Collection $config=NULL,array $data=[],bool $ceil=TRUE)
+ {
+ $map = collect(self::traffic_map);
+ $merge = collect(self::traffic_merge);
if (is_null($config))
$config = collect($config);
@@ -69,14 +153,12 @@ class AdslSupplierPlan extends Model
// If config is null, use the configuration from this Model
if (! $config->count()) {
// Base Config
- foreach ($map->keys() as $k) {
+ foreach ($map->keys() as $k)
$config->put($k,$this->{$k});
- }
// Excess Config
- foreach ($map->values() as $k) {
+ foreach ($map->values() as $k)
$config->put($k,$this->{$k});
- }
// Shaped or Charge
$config->put('shaped',$this->extra_shaped);
@@ -89,7 +171,7 @@ class AdslSupplierPlan extends Model
$result = collect();
// If data is empty, we'll report on allowance, otherwise we'll report on consumption
- $report = $data ? FALSE : TRUE;
+ $report = ! $data;
// Work out if we charge each period
foreach ($map as $k => $v) {
@@ -136,7 +218,12 @@ class AdslSupplierPlan extends Model
return $result;
}
- public function getNameAttribute()
+ /**
+ * Return the Broadband Speed
+ *
+ * @return string
+ */
+ public function speed(): string
{
return $this->speed;
}
diff --git a/app/Models/Supplier/Ethernet.php b/app/Models/Supplier/Ethernet.php
new file mode 100644
index 0000000..7db3244
--- /dev/null
+++ b/app/Models/Supplier/Ethernet.php
@@ -0,0 +1,7 @@
+'collection' ];
+
/* RELATIONS */
public function supplier()
diff --git a/app/Models/Tax.php b/app/Models/Tax.php
index b135a92..3120b4a 100644
--- a/app/Models/Tax.php
+++ b/app/Models/Tax.php
@@ -2,6 +2,7 @@
namespace App\Models;
+use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
class Tax extends Model
@@ -14,4 +15,30 @@ class Tax extends Model
{
return $this->belongsTo(Country::class);
}
+
+ /* METHODS */
+
+ /**
+ * Calculate Tax on a value
+ *
+ * @param float $value
+ * @param Collection $taxes
+ * @return void
+ */
+ public static function tax_calc(?float $value,Collection $taxes): float
+ {
+ if (! $value)
+ $value = 0;
+ $tax = 0;
+
+ foreach ($taxes as $o) {
+ // Quick sanity check
+ if (! $o instanceof self)
+ abort(500,'Invalid object for tax calculation');
+
+ $tax += round($value*$o->rate,2);
+ }
+
+ return round($value+$tax,2);
+ }
}
\ No newline at end of file
diff --git a/app/Models/User.php b/app/Models/User.php
index 879c57e..22fcaaf 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -495,7 +495,7 @@ class User extends Authenticatable
}
$result->load([
- 'product.descriptions',
+ 'product.description',
'service.type',
]);
diff --git a/app/Traits/ProductDetails.php b/app/Traits/ProductDetails.php
new file mode 100644
index 0000000..9ada39a
--- /dev/null
+++ b/app/Traits/ProductDetails.php
@@ -0,0 +1,21 @@
+define(App\Models\Product\Adsl::class, function (Faker $faker) {
+$factory->define(App\Models\Product\Broadband::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();
+$factory->afterMaking(App\Models\Product\Broadband::class, function ($adsl,$faker) {
+ $product = factory(App\Models\Supplier\Broadband::class)->make();
$adsl->setRelation('product',$product);
$adsl->adsl_supplier_plan_id = $product->id;
});
-$factory->state(App\Models\Product\Adsl::class,'unlimit',[
+$factory->state(App\Models\Product\Broadband::class,'unlimit',[
'base_down_peak'=>NULL,
'base_up_peak'=>NULL,
'base_down_offpeak'=>NULL,
@@ -29,7 +29,7 @@ $factory->state(App\Models\Product\Adsl::class,'unlimit',[
'metric'=>1,
]);
-$factory->state(App\Models\Product\Adsl::class,'140/0/0/0',[
+$factory->state(App\Models\Product\Broadband::class,'140/0/0/0',[
'base_down_peak'=>140,
'base_up_peak'=>0,
'base_down_offpeak'=>0,
@@ -43,7 +43,7 @@ $factory->state(App\Models\Product\Adsl::class,'140/0/0/0',[
'metric'=>1,
]);
-$factory->state(App\Models\Product\Adsl::class,'70/-/0/-',[
+$factory->state(App\Models\Product\Broadband::class,'70/-/0/-',[
'base_down_peak'=>70,
'base_up_peak'=>NULL,
'base_down_offpeak'=>0,
@@ -57,7 +57,7 @@ $factory->state(App\Models\Product\Adsl::class,'70/-/0/-',[
'metric'=>1,
]);
-$factory->state(App\Models\Product\Adsl::class,'100/0/40/0',[
+$factory->state(App\Models\Product\Broadband::class,'100/0/40/0',[
'base_down_peak'=>100,
'base_up_peak'=>0,
'base_down_offpeak'=>40,
@@ -71,7 +71,7 @@ $factory->state(App\Models\Product\Adsl::class,'100/0/40/0',[
'metric'=>1,
]);
-$factory->state(App\Models\Product\Adsl::class,'50/-/20/-',[
+$factory->state(App\Models\Product\Broadband::class,'50/-/20/-',[
'base_down_peak'=>50,
'base_up_peak'=>NULL,
'base_down_offpeak'=>20,
diff --git a/database/factories/AdslSupplierPlanFactory.php b/database/factories/AdslSupplierPlanFactory.php
index 4cfb7ad..49e3100 100644
--- a/database/factories/AdslSupplierPlanFactory.php
+++ b/database/factories/AdslSupplierPlanFactory.php
@@ -2,7 +2,7 @@
use Faker\Generator as Faker;
-$factory->define(App\Models\AdslSupplierPlan::class, function (Faker $faker) {
+$factory->define(App\Models\Supplier\Broadband::class, function (Faker $faker) {
return [
'id'=>1,
'contract_term'=>12,
diff --git a/database/factories/ProductFactory.php b/database/factories/ProductFactory.php
index 19b5beb..164a1a0 100644
--- a/database/factories/ProductFactory.php
+++ b/database/factories/ProductFactory.php
@@ -33,7 +33,6 @@ class ProductFactory extends Factory
'taxable' => TRUE,
'active' => TRUE,
// 'position'
- // 'cart_multiple'
// 'group_avail'
// 'avail_category'
// 'price_type'
@@ -50,7 +49,7 @@ class ProductFactory extends Factory
// 'price_recurr_weekday'
// 'price_recurr_strict'
// 'prod_plugin_file'
- //'prod_plugin_data' => 1,
+ // 'prod_plugin_data' => 1,
// 'accounting'
// 'model' => 'App\Models\Product\Adsl',
];
diff --git a/database/migrations/2021_12_20_000000_int_unsigned.php b/database/migrations/2021_12_20_000000_int_unsigned.php
index a9037b7..00b9110 100644
--- a/database/migrations/2021_12_20_000000_int_unsigned.php
+++ b/database/migrations/2021_12_20_000000_int_unsigned.php
@@ -10,9 +10,8 @@ use Illuminate\Support\Facades\Schema;
| ab_account_log |
| ab_account_memo |
| ab_account_oauth |
-| ab_adsl_plan |
-| ab_adsl_supplier |
-| ab_adsl_supplier_plan |
+| product_broadband |*DONE*
+| supplier_broadband |*DONE*
| ab_affiliate |
| ab_asset |
| ab_asset_pool |
@@ -48,7 +47,7 @@ use Illuminate\Support\Facades\Schema;
| ab_module_method_token |
| ab_oauth |
| ab_pivot_product_cat |
-| ab_product |
+| products |*PARTIAL* - make model/model_id NOT NULL
| ab_product_cat |
| ab_product_cat_translate |
| ab_product_translate |
@@ -71,7 +70,7 @@ use Illuminate\Support\Facades\Schema;
| ab_task |
| ab_task_log |
| ab_voip_plan |
-| accounts |*PARTIAL*
+| accounts |*PARTIAL* - make timestamp columns, make date_expire timestamp
| charges |
| countries |*DONE*
| currencies |*DONE*
@@ -98,7 +97,7 @@ use Illuminate\Support\Facades\Schema;
| supplier_details |*DONE*
| suppliers |*DONE*
| taxes |*DONE*
-| users |*PARTIAL*
+| users |*DONE*
*/
class IntUnsigned extends Migration
{
@@ -322,7 +321,8 @@ class IntUnsigned extends Migration
DB::statement('ALTER TABLE users MODIFY country_id int unsigned NOT NULL,MODIFY language_id int unsigned NOT NULL,MODIFY currency_id int unsigned NOT NULL');
Schema::table('users', function (Blueprint $table) {
- $table->foreign(['country_id','currency_id'])->references(['id','currency_id'])->on('countries');
+ $table->dropColumn(['currency_id']);
+ $table->foreign(['country_id'])->references(['id'])->on('countries');
$table->foreign(['parent_id','site_id'])->references(['id','site_id'])->on('users');
$table->foreign(['language_id'])->references(['id'])->on('languages');
});
@@ -332,11 +332,11 @@ class IntUnsigned extends Migration
DB::statement('ALTER TABLE accounts MODIFY site_id int unsigned NOT NULL');
DB::statement('ALTER TABLE accounts MODIFY country_id int unsigned NOT NULL,MODIFY language_id int unsigned NOT NULL,MODIFY currency_id int unsigned NOT NULL,MODIFY rtm_id int unsigned DEFAULT NULL,MODIFY active tinyint(1) NOT NULL');
Schema::table('accounts', function (Blueprint $table) {
+ $table->dropColumn(['currency_id','language_id']);
$table->foreign(['site_id'])->references(['site_id'])->on('sites');
$table->index(['id','site_id']);
- $table->foreign(['country_id','currency_id'])->references(['id','currency_id'])->on('countries');
+ $table->foreign(['country_id'])->references(['id'])->on('countries');
$table->foreign(['user_id','site_id'])->references(['id','site_id'])->on('users');
- $table->foreign(['language_id'])->references(['id'])->on('languages');
$table->foreign(['rtm_id','site_id'])->references(['id','site_id'])->on('rtm');
});
diff --git a/database/migrations/2021_12_20_225017_optimize_product.php b/database/migrations/2021_12_20_225017_optimize_product.php
new file mode 100644
index 0000000..11f9683
--- /dev/null
+++ b/database/migrations/2021_12_20_225017_optimize_product.php
@@ -0,0 +1,295 @@
+dropForeign(['site_id']);
+ $table->dropIndex(['id','site_id']);
+ $table->dropIndex('ab_product_site_id_foreign');
+ });
+
+ DB::statement('ALTER TABLE ab_product RENAME TO products');
+
+ Schema::table('products', function (Blueprint $table) {
+ $table->dropColumn(['cart_multiple']);
+ $table->index(['id','site_id']);
+ $table->foreign(['site_id'])->references(['id'])->on('sites');
+ $table->dateTime('created_at')->nullable()->after('id');
+ $table->dateTime('updated_at')->nullable()->after('created_at');
+ });
+
+ Schema::table('supplier_details', function (Blueprint $table) {
+ $table->jsonb('connections')->nullable();
+ });
+
+ foreach (\Illuminate\Support\Facades\DB::select('SELECT * FROM AB_ADSL_SUPPLIER') as $o) {
+ switch($o->name) {
+ case 'PeopleAgent':
+ $type = 'broadband';
+ $name = 'People Telecom';
+ break;
+
+ case 'iiNetADSL':
+ $type = 'broadband';
+ $name = 'iiNet';
+ break;
+
+ case 'ExetelVisp':
+ $type = 'broadband';
+ $name = 'Exetel';
+ break;
+
+ case 'ExetelHSPA':
+ $type = 'hspa';
+ $name = 'Exetel';
+ break;
+
+ case 'ExetelPE':
+ $type = 'ethernet';
+ $name = 'Exetel';
+ break;
+
+ default:
+ throw new Exception('Unknown Supplier: '.$o->name);
+ }
+
+ $so = \App\Models\Supplier::where('name',$name)->singleOrNew();
+
+ if (! $so->exists) {
+ $so->name = $name;
+ $so->address1 = '...';
+ $so->city = '...';
+ $so->state = '...';
+ $so->postcode = '...';
+ }
+
+ $so->active = $so->active ?: $o->active;
+ $so->save();
+
+ $sdo = \App\Models\SupplierDetail::withoutGlobalScope(\App\Models\Scopes\SiteScope::class)
+ ->where('supplier_id',$so->id)
+ ->where('site_id',$o->site_id)
+ ->firstOrNew();
+
+ if (! $sdo->exists) {
+ $sdo->site_id = $o->site_id;
+ }
+
+ $connections = $sdo->connections ?: collect();
+
+ $connections->put($type,[
+ 'user'=>$o->stats_username,
+ 'pass'=>$o->stats_password,
+ 'last'=>$o->stats_lastupdate,
+ 'url'=>$o->stats_url,
+ ]);
+
+ $sdo->connections = $connections;
+
+ $so->detail()->save($sdo);
+ };
+
+ Schema::table('ab_adsl_supplier_plan', function (Blueprint $table) {
+ $table->dropForeign(['site_id']);
+ $table->dropIndex(['id','site_id']);
+ $table->dropIndex('ab_adsl_supplier_plan_site_id_foreign');
+ });
+
+ DB::statement('ALTER TABLE ab_adsl_supplier_plan RENAME TO supplier_broadband');
+ DB::statement('ALTER TABLE supplier_broadband MODIFY product_id varchar(16) NOT NULL');
+ DB::statement('ALTER TABLE supplier_broadband MODIFY base_cost double NOT NULL');
+ DB::statement('ALTER TABLE supplier_broadband MODIFY active tinyint(1)');
+ DB::statement('ALTER TABLE supplier_broadband RENAME COLUMN supplier_id TO old_supplier_id');
+ DB::statement('ALTER TABLE supplier_broadband RENAME COLUMN offpeak_start TO old_offpeak_start');
+ DB::statement('ALTER TABLE supplier_broadband RENAME COLUMN offpeak_end TO old_offpeak_end');
+
+ Schema::table('supplier_broadband', function (Blueprint $table) {
+ $table->index(['id','site_id']);
+ $table->foreign(['site_id'])->references(['id'])->on('sites');
+ $table->dateTime('created_at')->nullable()->after('id');
+ $table->dateTime('updated_at')->nullable()->after('created_at');
+ $table->time('offpeak_start')->nullable()->after('old_offpeak_end');
+ $table->time('offpeak_end')->nullable()->after('offpeak_start');
+ });
+
+ Schema::table('supplier_broadband', function (Blueprint $table) {
+ $table->integer('supplier_detail_id')->unsigned()->nullable()->after('old_supplier_id');
+
+ $table->foreign(['supplier_detail_id','site_id'])->references(['id','site_id'])->on('supplier_details');
+ });
+
+ \Illuminate\Support\Facades\DB::select("UPDATE ab_service SET model='App\\\\Models\\\\Service\\\\Broadband' where model='App\\\\Models\\\\Service\\\\Adsl'");
+ \Illuminate\Support\Facades\DB::select("UPDATE products SET model='App\\\\Models\\\\Product\\\\Broadband' where model='App\\\\Models\\\\Product\\\\Adsl'");
+
+ // Convert to use the new supplier
+ foreach (\Illuminate\Support\Facades\DB::select('SELECT * FROM AB_ADSL_SUPPLIER') as $o) {
+ switch ($o->name) {
+ case 'PeopleAgent':
+ $so = \App\Models\Supplier::where('name','People Telecom')->singleOrFail();
+ break;
+
+ case 'iiNetADSL':
+ $so = \App\Models\Supplier::where('name','iiNet')->singleOrFail();
+ break;
+
+ case 'ExetelVisp':
+ case 'ExetelHSPA':
+ case 'ExetelPE':
+ $so = \App\Models\Supplier::where('name','Exetel')->singleOrFail();
+ break;
+
+ default:
+ throw new Exception('Unknown Supplier: ' . $o->name);
+ }
+
+ $sdo = \App\Models\SupplierDetail::withoutGlobalScope(\App\Models\Scopes\SiteScope::class)
+ ->where('supplier_id',$so->id)
+ ->where('site_id',$o->site_id)
+ ->singleOrFail();
+
+ \Illuminate\Support\Facades\DB::select(sprintf("UPDATE supplier_broadband SET supplier_detail_id=%d where old_supplier_id=%d and site_id=%d",$sdo->id,$o->id,$sdo->site_id));
+ }
+
+ // Convert out dates
+ foreach (\App\Models\Supplier\Broadband::withoutGlobalScope(\App\Models\Scopes\SiteScope::class)->cursor() as $o) {
+ if ($o->date_orig)
+ $o->created_at = \Carbon\Carbon::createFromTimestamp($o->date_orig);
+ if ($o->date_last)
+ $o->updated_at = \Carbon\Carbon::createFromTimestamp($o->date_last);
+ if ($o->old_offpeak_start)
+ $o->offpeak_start = \Carbon\Carbon::createFromTimestamp($o->old_offpeak_start);
+ if ($o->old_offpeak_end)
+ $o->offpeak_end = \Carbon\Carbon::createFromTimestamp($o->old_offpeak_end);
+ $o->save();
+ }
+
+ Schema::table('supplier_broadband', function (Blueprint $table) {
+ $table->dropPrimary();
+ $table->primary(['id','site_id']);
+ $table->dropColumn(['date_orig','date_last','old_supplier_id','old_offpeak_start','old_offpeak_end']);
+ });
+ Schema::dropIfExists('ab_adsl_supplier');
+
+ DB::statement('ALTER TABLE supplier_broadband MODIFY extra_charged tinyint(1)');
+ DB::statement('ALTER TABLE supplier_broadband MODIFY extra_shaped tinyint(1)');
+ DB::statement('ALTER TABLE supplier_broadband MODIFY contract_term int unsigned');
+
+ Schema::table('ab_adsl_plan', function (Blueprint $table) {
+ $table->dropForeign(['site_id']);
+ $table->dropIndex(['id','site_id']);
+ $table->dropIndex('ab_adsl_plan_site_id_foreign');
+ });
+
+ DB::statement('ALTER TABLE ab_adsl_plan RENAME TO product_broadband');
+ DB::statement('ALTER TABLE product_broadband DROP PRIMARY KEY,ADD PRIMARY KEY (id,site_id)');
+ DB::statement('ALTER TABLE product_broadband MODIFY extra_charged tinyint(1)');
+ DB::statement('ALTER TABLE product_broadband MODIFY extra_shaped tinyint(1)');
+ DB::statement('ALTER TABLE product_broadband MODIFY contract_term int unsigned');
+ DB::statement('ALTER TABLE product_broadband RENAME COLUMN adsl_supplier_plan_id TO supplier_broadband_id');
+ DB::statement('ALTER TABLE product_broadband MODIFY supplier_broadband_id int unsigned');
+
+ Schema::table('product_broadband', function (Blueprint $table) {
+ $table->index(['id','site_id']);
+ $table->foreign(['site_id'])->references(['id'])->on('sites');
+ $table->foreign(['supplier_broadband_id','site_id'])->references(['id','site_id'])->on('supplier_broadband');
+ $table->dateTime('created_at')->nullable()->after('id');
+ $table->dateTime('updated_at')->nullable()->after('created_at');
+ });
+
+ // Convert product pricegroups
+ foreach (\App\Models\Product::withoutGlobalScope(\App\Models\Scopes\SiteScope::class)->cursor() as $po) {
+ if ($po->date_orig)
+ $po->created_at = \Carbon\Carbon::createFromTimestamp($po->date_orig);
+ if ($po->date_last)
+ $po->updated_at = \Carbon\Carbon::createFromTimestamp($po->date_last);
+
+ if (! ($po instanceof \Illuminate\Support\Collection) || ! $po->price_group->count()) {
+ $original = $po->getRawOriginal('price_group');
+
+ // serialized
+ if (preg_match('/^a:/',$original)) {
+ try {
+ $price_group = collect(unserialize(str_replace("\n","",$original)));
+ } catch (Exception $e) {
+ dd(['error'=>$e->getMessage(),'raw'=>$po->getRawOriginal('price_group')]);
+ }
+
+ } elseif (is_null($po->getRawOriginal('price_group'))) {
+ $price_group = collect();
+
+ } else {
+ try {
+ $price_group = unserialize(gzuncompress($po->getRawOriginal('price_group')));
+ } catch (Exception $e) {
+ dd(['error'=>$e->getMessage(),'raw'=>$po->getRawOriginal('price_group')]);
+ }
+ }
+
+ $new_price_group = collect();
+
+ // Remove any blank entries, or when base/setup = 0
+ foreach ($price_group as $group => $values) {
+ $build = collect();
+
+ foreach ($values as $key => $pricing) {
+ switch ($key) {
+ case 'show':
+ $build->put('show',(bool) $pricing);
+ break;
+
+ default:
+ // key is a time period
+ if ((! Arr::get($pricing,'price_base')) && (! Arr::get($pricing,'price_setup')))
+ break;
+
+ $build->put($key,[
+ 'base'=>Arr::get($pricing,'price_base'),
+ 'setup'=>Arr::get($pricing,'price_setup'),
+ ]);
+ }
+ }
+
+ $new_price_group->put($group,$build);
+ }
+
+ $po->price_group = $new_price_group;
+ }
+
+ $po->save();
+ }
+
+ DB::statement('ALTER TABLE products MODIFY taxable tinyint(1),MODIFY active tinyint(1),MODIFY price_recurr_strict tinyint(1),MODIFY prod_plugin_data int unsigned');
+ DB::statement('ALTER TABLE products RENAME COLUMN price_group TO pricing');
+ DB::statement('ALTER TABLE products RENAME COLUMN price_recurr_default TO price_recur_default');
+ DB::statement('ALTER TABLE products RENAME COLUMN price_recurr_strict TO price_recur_strict');
+ DB::statement('ALTER TABLE products RENAME COLUMN prod_plugin_data TO model_id');
+ Schema::table('products', function (Blueprint $table) {
+ $table->dropColumn(['date_orig','date_last','group_avail','avail_category','price_recurr_day','price_recurr_weekday','prod_plugin_file']);
+ });
+ */
+
+ abort(500,'here');
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ //abort(500,'Cant go back');
+ }
+}
diff --git a/public/css/fixes.css b/public/css/fixes.css
index 9081cfb..cbc5d4b 100644
--- a/public/css/fixes.css
+++ b/public/css/fixes.css
@@ -19,6 +19,12 @@ body {
box-shadow: 0 0 0 #fff;
background-color: #fff;
}
+
+/* Fix select width */
+.dataTables_wrapper .dataTables_length select {
+ width: 5em !important;
+}
+
.dataTables_scrollHeadInner {
width: 100% !important;
}
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 1be797d..05e1698 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
@@ -1,32 +1,80 @@
+
+
+
+
+ {{ $o->product->type->supplied->supplier_detail->supplier->name }}
+ Us
+
+
+
+
+
- Supplier {{ $o->product->type ? $o->product->type->supplier->name : 'Supplier Unknown' }}
+ Product
+ #{{ ($s=$o->product->type->supplied)->id }}: {{ $s->name }}
+ #{{ $o->product->id }}: {{ $o->product->name }}
+ {{ $s->type }}
-
- Supplier Product #{{ $o->product_id }}: {{ $o->product->type ? $o->product->type->product->product_id : 'Product Unknown' }}
-
- @if($o->product->type)
-
-
- 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) }}
-
- @endif
-
- Price ${{ number_format($o->billing_monthly_price,2) }} (${{ number_format($o->billing_monthly_price*12,2) }} Annually)
-
- @if($o->product->type AND $o->product->type->cost)
-
- Markup {{ number_format(($o->billing_monthly_price/$o->product->type->cost-1)*100,2) }}%
-
- @endif
+
+ Setup
+ ${{ number_format($a=\App\Models\Tax::tax_calc($s->setup_cost,$o->account->taxes),2) }}
+ ${{ 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) }})%
+ @else
+ {{ number_format(($b-$a)/($a ?: 1)*100,1) }}%
+ @endif
+
+
+
+ Billed
+ {{ $s->billing_interval_string }}
+ {{ $o->product->billing_interval_string }}
+
+
+
+ Billing Charge
+ ${{ number_format($a=\App\Models\Tax::tax_calc($s->base_cost*\App\Models\Invoice::billing_change($s->billing_interval,$o->product->billing_interval),$o->account->taxes),2) }}
+ ${{ 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) }})%
+ @else
+ {{ number_format(($b-$a)/($a ?: 1)*100,1) }}%
+ @endif
+
+
+
+ Monthly Cost
+ ${{ number_format($a=\App\Models\Tax::tax_calc($s->base_cost*\App\Models\Invoice::billing_change($s->billing_interval,1),$o->account->taxes),2) }}
+ ${{ 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) }})%
+ @else
+ {{ number_format(($b-$a)/($a ?: 1)*100,1) }}%
+ @endif
+
+
+
+ Contract
+ {{ $s->contract_term }} months
+ {{ $o->product->contract_term }} months
+
+
+
+ Min Cost
+ ${{ number_format($a=\App\Models\Tax::tax_calc($s->min_cost,$o->account->taxes),2) }}
+ ${{ 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) }})%
+ @else
+ {{ number_format(($b-$a)/($a ?: 1)*100,1) }}%
+ @endif
+
+
+
\ No newline at end of file
diff --git a/resources/views/theme/backend/adminlte/a/supplier/details.blade.php b/resources/views/theme/backend/adminlte/a/supplier/details.blade.php
new file mode 100644
index 0000000..65112eb
--- /dev/null
+++ b/resources/views/theme/backend/adminlte/a/supplier/details.blade.php
@@ -0,0 +1,57 @@
+@extends('adminlte::layouts.app')
+
+@section('htmlheader_title')
+ {{ $o->name ?: 'New Supplier' }}
+@endsection
+@section('page_title')
+ {{ $o->name ?: 'New Supplier' }}
+@endsection
+
+@section('contentheader_title')
+ {{ $o->name ?: 'New Supplier' }}
+@endsection
+@section('contentheader_description')
+@endsection
+
+@section('main-content')
+
+
+ @include('adminlte::widget.status')
+
+
+
+
+
+
+
+
+
+
+
+ @include('a.supplier.widgets.detail')
+
+
+
+ @include('a.supplier.widgets.products')
+
+
+
+ @include('a.supplier.widgets.offerings')
+
+
+
+ @include('a.supplier.widgets.connections')
+
+
+
+
+
+
+@endsection
\ No newline at end of file
diff --git a/resources/views/theme/backend/adminlte/a/supplier.blade.php b/resources/views/theme/backend/adminlte/a/supplier/find.blade.php
similarity index 100%
rename from resources/views/theme/backend/adminlte/a/supplier.blade.php
rename to resources/views/theme/backend/adminlte/a/supplier/find.blade.php
diff --git a/resources/views/theme/backend/adminlte/a/supplier/widgets/connections.blade.php b/resources/views/theme/backend/adminlte/a/supplier/widgets/connections.blade.php
new file mode 100644
index 0000000..3c96bf5
--- /dev/null
+++ b/resources/views/theme/backend/adminlte/a/supplier/widgets/connections.blade.php
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+ Service Type
+ User Name
+ Password
+ URL
+ Last Connect
+ Offerings
+
+
+
+
+ @foreach ($o->offeringTypes() as $key => $offering)
+
+ {{ $offering->type }}
+ @if(Arr::get($o->detail->connections,$key))
+ {{ Arr::get($o->detail->connections,$key.'.user') }}
+ {{ Arr::get($o->detail->connections,$key.'.pass') }}
+ {{ Arr::get($o->detail->connections,$key.'.url') }}
+ {{ \Carbon\Carbon::createFromFormat('Y-m-d',Arr::get($o->detail->connections,$key.'.last'))->format('Y-m-d') }}
+ {{ number_format($offering->items->count()) }}
+ @else
+
+ @endif
+
+ @endforeach
+
+
+
+
\ 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
new file mode 100644
index 0000000..b0f5d4c
--- /dev/null
+++ b/resources/views/theme/backend/adminlte/a/supplier/widgets/detail.blade.php
@@ -0,0 +1,185 @@
+
+
+
+
Supplier Details
+
+ @if(session()->has('success'))
+
{{ session()->get('success') }}
+ @endif
+
+
+
+
\ No newline at end of file
diff --git a/resources/views/theme/backend/adminlte/a/supplier/widgets/offerings.blade.php b/resources/views/theme/backend/adminlte/a/supplier/widgets/offerings.blade.php
new file mode 100644
index 0000000..f0f12a7
--- /dev/null
+++ b/resources/views/theme/backend/adminlte/a/supplier/widgets/offerings.blade.php
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+ @foreach($o->offeringTypes() as $key => $offering)
+
+
+
+
+ Product
+ Services
+
+
+ ID
+ Product ID
+ Product Name
+ Active
+ Setup Cost
+ Base Cost
+ Types
+ Products
+ Sold
+ Active
+
+
+
+
+ @foreach($xx=$offering->items->with(['types.product.services'])->get() as $oo)
+
+ {{ $oo->id }}
+ {{ $oo->name }}
+ {{ $oo->name_long }}
+ {{ $oo->active ? 'YES' : 'NO' }}
+ {{ number_format($oo->setup_cost_taxable,2) }}
+ {{ number_format($oo->base_cost_taxable,2) }}
+ {{ number_format($oo->types->count()) }}
+ {{ number_format($oo->types->pluck('product')->filter()->count()) }}
+ {{ number_format(($x=$oo->types->pluck('product.services')->flatten()->filter())->count()) }}
+ {{ number_format($x->where('active')->count()) }}
+
+ @endforeach
+
+
+
+
+ TOTALS
+ {{ $xx->where('active',TRUE)->count() }}
+ {{ number_format(($x=$xx->pluck('types')->flatten()->filter())->count()) }}
+ {{ number_format($x->pluck('product')->filter()->count()) }}
+ {{ number_format(($xxx=$x->pluck('product.services')->flatten()->filter())->count()) }}
+ {{ number_format($xxx->where('active')->count()) }}
+
+
+
+
+ @endforeach
+
+
+
+
+@section('page-scripts')
+ @css(datatables,bootstrap4)
+ @js(datatables,bootstrap4)
+
+
+@append
\ No newline at end of file
diff --git a/resources/views/theme/backend/adminlte/a/supplier/widgets/products.blade.php b/resources/views/theme/backend/adminlte/a/supplier/widgets/products.blade.php
new file mode 100644
index 0000000..146c6bc
--- /dev/null
+++ b/resources/views/theme/backend/adminlte/a/supplier/widgets/products.blade.php
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+ @foreach($o->offeringTypes() as $key => $offering)
+
+
+
+
+ Product
+ Services
+
+
+ ID
+ Product ID
+ Product Name
+ Active
+ Default Billing
+ Setup Cost
+ Base Cost
+ Setup Charge
+ Base Charge
+ Sold
+ Active
+
+
+
+
+ @foreach($xx=$offering->items->with(['types.product.services','types.product.type.supplied','types.product.description'])->get() as $oo)
+ @foreach($oo->types->pluck('product')->filter() as $po)
+
+
+ {{ $po->lid }}
+ {{ $po->name_short }}
+ {{ $po->name }}
+ {{ $po->active ? 'YES' : 'NO' }}
+ {{ $po->billing_interval_string }}
+ {{ number_format($po->setup_cost_taxable,2) }}
+ {{ number_format($po->base_cost_taxable,2) }}
+ {{ number_format($po->setup_charge_taxable,2) }}
+ {{ number_format($po->base_charge_taxable,2) }}
+ {{ number_format($po->services->count()) }}
+ {{ number_format($po->services->where('active')->count()) }}
+
+ @endforeach
+ @endforeach
+
+
+
+
+ TOTALS
+ {{ $xx->where('active',TRUE)->count() }}
+
+ {{ number_format(($xxx=$xx->pluck('types')->flatten()->pluck('product.services')->flatten()->filter())->count()) }}
+ {{ number_format($xxx->where('active')->count()) }}
+
+
+
+
+ @endforeach
+
+
+
+
+@section('page-scripts')
+ @css(datatables,bootstrap4)
+ @js(datatables,bootstrap4)
+
+
+@append
\ No newline at end of file
diff --git a/resources/views/theme/backend/adminlte/a/supplierdetails.blade.php b/resources/views/theme/backend/adminlte/a/supplierdetails.blade.php
deleted file mode 100644
index 0a64a95..0000000
--- a/resources/views/theme/backend/adminlte/a/supplierdetails.blade.php
+++ /dev/null
@@ -1,225 +0,0 @@
-@extends('adminlte::layouts.app')
-
-@section('htmlheader_title')
- {{ $o->name ?: 'New Supplier' }}
-@endsection
-@section('page_title')
- {{ $o->name ?: 'New Supplier' }}
-@endsection
-
-@section('contentheader_title')
- {{ $o->name ?: 'New Supplier' }}
-@endsection
-@section('contentheader_description')
-@endsection
-
-@section('main-content')
-
-
- @include('adminlte::widget.status')
-
-
-
-
-
-
-
-
-
-
-
-
Supplier Details
-
- @if(session()->has('success'))
-
{{ session()->get('success') }}
- @endif
-
-
- @csrf
-
-
-
-
-
-
-
- Supplier Name
-
-
- @error('name')
- {{ $message }}
- @else
- Supplier Name required.
- @enderror
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- City
-
-
- @error('city')
- {{ $message }}
- @else
- City is required.
- @enderror
-
-
-
-
-
-
-
-
-
- State
-
-
- @error('state')
- {{ $message }}
- @else
- State is required.
- @enderror
-
-
-
-
-
-
-
- Postal Code
-
-
- @error('postcode')
- {{ $message }}
- @else
- Postcode is required.
- @enderror
-
-
-
-
-
-
-
-
-
-
-
- Accounts Email
-
-
- @error('supplier_details.accounts')
- {{ $message }}
- @enderror
-
-
-
-
-
-
-
-
-
- Support Email
-
-
- @error('supplier_details.support')
- {{ $message }}
- @enderror
-
-
-
-
-
-
-
-
-
- Payment Details
-
-
- @error('supplier_details.payments')
- {{ $message }}
- @enderror
-
-
-
-
-
-
-
-
-
-
-
- Notes
- {{ old('supplier_details.notes',($o->detail ? $o->detail->notes : '')) }}
- Notes.
-
- @error('supplier_details.notes')
- {{ $message }}
- @enderror
-
-
-
-
-
-
-
-
-
Cancel
- @can('wholesaler')
-
@if ($o->exists)Save @else Add @endif
- @endcan
-
-
-
-
-
- Products.
-
-
-
-
-
-
-@endsection
\ No newline at end of file
diff --git a/resources/views/theme/backend/adminlte/u/invoice/home.blade.php b/resources/views/theme/backend/adminlte/u/invoice/home.blade.php
index 5f75c5a..b358f74 100644
--- a/resources/views/theme/backend/adminlte/u/invoice/home.blade.php
+++ b/resources/views/theme/backend/adminlte/u/invoice/home.blade.php
@@ -102,7 +102,7 @@
{{ $po->count }}
#{{ $po->lid }}
- {{ $po->name($o->account->user->language) }}
+ {{ $po->name }}
${{ number_format($o->items->filter(function($item) use ($po) {return $item->product_id == $po->id; })->sum('total'),$o->currency()->rounding) }}
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 1384fe8..06e101e 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,8 @@
Emails
--}}
@can('wholesaler')
- Internal
+ Billing History
+ Internal
Update
@endcan
diff --git a/resources/views/theme/backend/adminlte/u/service/widgets/active.blade.php b/resources/views/theme/backend/adminlte/u/service/widgets/active.blade.php
index d648925..154affe 100644
--- a/resources/views/theme/backend/adminlte/u/service/widgets/active.blade.php
+++ b/resources/views/theme/backend/adminlte/u/service/widgets/active.blade.php
@@ -10,7 +10,7 @@
ID
- Category
+ Type
Service
Product
Next Invoice
@@ -21,9 +21,9 @@
@foreach ($o->services as $oo)
{{ $oo->sid }}
- {{ $oo->product_category }}
+ {{ $oo->service_type }}
{{ $oo->name_short }}
- {{ $oo->product_name }}
+ {{ $oo->product->name }}
{{ $oo->external_billing ? '-' : $oo->next_invoice->format('Y-m-d') }}
@endforeach
diff --git a/resources/views/theme/backend/adminlte/u/service/widgets/broadband/change.blade.php b/resources/views/theme/backend/adminlte/u/service/widgets/broadband/change.blade.php
index 9201de6..c00a5a0 100644
--- a/resources/views/theme/backend/adminlte/u/service/widgets/broadband/change.blade.php
+++ b/resources/views/theme/backend/adminlte/u/service/widgets/broadband/change.blade.php
@@ -7,7 +7,7 @@
@php
$po = $selected = NULL;
@endphp
- @foreach (\App\Models\Product::active()->get()->filter(function($item) { return $item->type && (get_class($item->type) == 'App\Models\Product\Adsl'); })->sortBy('name') as $o)
+ @foreach (\App\Models\Product::active()->get()->filter(function($item) { return $item->type && (get_class($item->type) == 'App\Models\Product\Broadband'); })->sortBy('name') as $o)
@php
if ($o->id == old('product_id')) {
$selected = 'selected';
diff --git a/resources/views/theme/backend/adminlte/u/service/widgets/broadband/details.blade.php b/resources/views/theme/backend/adminlte/u/service/widgets/broadband/details.blade.php
index 6758be0..7b3d60f 100644
--- a/resources/views/theme/backend/adminlte/u/service/widgets/broadband/details.blade.php
+++ b/resources/views/theme/backend/adminlte/u/service/widgets/broadband/details.blade.php
@@ -1,3 +1,4 @@
+
@if($o->service->isPending())
@@ -44,7 +45,7 @@
@endif
Speed
- {{ $o->service->product->type->product->speed }} Mbps
+ {{ $o->supplied()->speed() }} Mbps
diff --git a/resources/views/theme/frontend/metronic/order/home.blade.php b/resources/views/theme/frontend/metronic/order/home.blade.php
index ca960fc..49b0e72 100644
--- a/resources/views/theme/frontend/metronic/order/home.blade.php
+++ b/resources/views/theme/frontend/metronic/order/home.blade.php
@@ -9,7 +9,7 @@
Order Service
- @if ($errors->count()))
There were errors with your order, please try again. @endif
+ @if ($errors->count())
There were errors with your order, please try again. @endif
diff --git a/resources/views/theme/frontend/metronic/order/widget/info.blade.php b/resources/views/theme/frontend/metronic/order/widget/info.blade.php
index 13f8da0..2f6027b 100644
--- a/resources/views/theme/frontend/metronic/order/widget/info.blade.php
+++ b/resources/views/theme/frontend/metronic/order/widget/info.blade.php
@@ -1,8 +1,9 @@
-@if(View::exists('order.widget.info.'.$o->category))
+
+@if(View::exists('order.widget.info.'.strtolower($o->product_type)))
{{-- Return Category Requirements --}}
- @include('order.widget.info.'.$o->category)
+ @include('order.widget.info.'.strtolower($o->product_type))
{{-- Return Supplier Requirements --}}
{{-- Return Product Requirements --}}
diff --git a/resources/views/theme/frontend/metronic/order/widget/info/VOIP.blade.php b/resources/views/theme/frontend/metronic/order/widget/info/VOIP.blade.php
deleted file mode 100644
index 8598a8e..0000000
--- a/resources/views/theme/frontend/metronic/order/widget/info/VOIP.blade.php
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
{!! $o->description !!}
-
-
-
-
- Type
- VOIP
-
-
- Setup Charges
- ${{ is_numeric($o->setup_cost) ? number_format($o->setup_cost,2) : $o->setup_cost }}
-
-
- Cost
- ${{ is_numeric($o->default_cost) ? number_format($o->default_cost,2) : $o->default_cost }}
-
-
- Default Billing
- {{ is_numeric($o->default_billing) ? number_format($o->default_billing,2) : $o->default_billing }}
-
-
- Contract Term
- {{ $o->contract_term }} mths
-
-
- Minimum Costs
- ${{ is_numeric($o->minimum_cost) ? number_format($o->minimum_cost,2) : $o->minimum_cost }}
-
-
\ No newline at end of file
diff --git a/resources/views/theme/frontend/metronic/order/widget/info/ADSL.blade.php b/resources/views/theme/frontend/metronic/order/widget/info/broadband.blade.php
similarity index 57%
rename from resources/views/theme/frontend/metronic/order/widget/info/ADSL.blade.php
rename to resources/views/theme/frontend/metronic/order/widget/info/broadband.blade.php
index 8855275..914b46c 100644
--- a/resources/views/theme/frontend/metronic/order/widget/info/ADSL.blade.php
+++ b/resources/views/theme/frontend/metronic/order/widget/info/broadband.blade.php
@@ -1,3 +1,4 @@
+
@@ -9,15 +10,15 @@
Setup Charges *
- ${{ is_numeric($o->setup_cost) ? number_format($o->setup_cost,2) : $o->setup_cost }}
+ ${{ number_format($o->setup_charge_taxable,2) }}
Cost +
- ${{ is_numeric($o->default_cost) ? number_format($o->default_cost,2) : $o->default_cost }}
+ ${{ number_format($o->base_charge_taxable,2) }}
Default Billing
- {{ is_numeric($o->default_billing) ? number_format($o->default_billing,2) : $o->default_billing }}
+ {{ $o->billing_interval_string }}
Contract Term
@@ -25,7 +26,7 @@
Minimum Costs +*
- ${{ is_numeric($o->minimum_cost) ? number_format($o->minimum_cost,2) : $o->minimum_cost }}
+ ${{ number_format($o->min_charge_taxable,2) }}
diff --git a/resources/views/theme/frontend/metronic/order/widget/info/voip.blade.php b/resources/views/theme/frontend/metronic/order/widget/info/voip.blade.php
new file mode 100644
index 0000000..d2c4f01
--- /dev/null
+++ b/resources/views/theme/frontend/metronic/order/widget/info/voip.blade.php
@@ -0,0 +1,31 @@
+
+
+
{!! $o->description !!}
+
+
+
+
+ Type
+ {{ $o->product_type }}
+
+
+ Setup Charges
+ ${{ number_format($o->setup_charge_taxable,2) }}
+
+
+ Cost
+ ${{ number_format($o->base_charge_taxable,2) }}
+
+
+ Default Billing
+ {{ $o->billing_interval_string }}
+
+
+ Contract Term
+ {{ $o->contract_term }} mths
+
+
+ Minimum Costs
+ ${{ number_format($o->min_charge_taxable,2) }}
+
+
\ No newline at end of file
diff --git a/resources/views/theme/frontend/metronic/order/widget/order.blade.php b/resources/views/theme/frontend/metronic/order/widget/order.blade.php
index f28de81..e8661c9 100644
--- a/resources/views/theme/frontend/metronic/order/widget/order.blade.php
+++ b/resources/views/theme/frontend/metronic/order/widget/order.blade.php
@@ -1,4 +1,5 @@
-@if(View::exists('order.widget.order.'.$o->category))
+
+@if(View::exists('order.widget.order.'.strtolower($o->product_type)))