Work on products, first completed broadband

This commit is contained in:
Deon George
2021-12-24 12:14:01 +11:00
parent 8f5293662e
commit 1e9f15b40f
62 changed files with 2139 additions and 894 deletions

View File

@@ -7,25 +7,22 @@ use Illuminate\Database\Eloquent\Model;
use Leenooks\Traits\ScopeActive;
use App\Interfaces\IDs;
use App\Traits\NextKey;
/**
* Class Account
* Service Accounts
*
* Attributes for accounts:
* + lid: : Local ID for account
* + sid: : System ID for account
* + name: : Account Name
* + lid : Local ID for account
* + sid : System ID for account
* + name : Account Name
* + taxes : Taxes Applicable to this account
*
* @package App\Models
*/
class Account extends Model implements IDs
{
use HasFactory,NextKey,ScopeActive;
const RECORD_ID = 'account';
public $incrementing = FALSE;
use HasFactory,ScopeActive;
const CREATED_AT = 'date_orig';
const UPDATED_AT = 'date_last';
@@ -67,6 +64,11 @@ class Account extends Model implements IDs
return $this->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);

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AccountGroup extends Model
{
protected $table = 'ab_account_group';
public $timestamps = FALSE;
}

View File

@@ -1,36 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Traits\OrderServiceOptions;
class AdslPlan extends Model
{
use OrderServiceOptions;
protected $table = 'ab_adsl_plan';
protected $order_attributes = [
'options.address'=>[
'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');
}
}

View File

@@ -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';

View File

@@ -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';
}

View File

@@ -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;
});
}

View File

@@ -1,41 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Traits\OrderServiceOptions;
class PlanVoip extends Model
{
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 = Service\Voip::class;
}

View File

@@ -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);
}
}

View File

@@ -1,159 +0,0 @@
<?php
namespace App\Models\Product;
use Illuminate\Support\Collection;
use App\Interfaces\ProductSupplier;
use App\Models\Base\ProductType;
use App\Models\AdslSupplier;
use App\Models\AdslSupplierPlan;
use App\Traits\NextKey;
class Adsl extends ProductType implements ProductSupplier
{
use NextKey;
const RECORD_ID = 'adsl_plan';
protected $table = 'ab_adsl_plan';
public static $map = [
'base_up_offpeak'=>'extra_up_offpeak',
'base_down_offpeak'=>'extra_down_offpeak',
'base_up_peak'=>'extra_up_peak',
'base_down_peak'=>'extra_down_peak',
];
/**
* Map upstream metrics into traffic allowance metrics
*
* @var array
*/
public static $metrics = [
'down_peak'=>'base_down_peak',
'down_offpeak'=>'base_down_offpeak',
'up_peak'=>'base_up_peak',
'up_offpeak'=>'base_up_offpeak',
'peer'=>'base_down_peak',
'internal'=>'base_down_offpeak',
];
/**
* The suppliers product
*
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function product()
{
return $this->hasOne(AdslSupplierPlan::class,'id','adsl_supplier_plan_id');
}
/**
* The supplier
*
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
*/
public function supplier()
{
return $this->hasOneThrough(AdslSupplier::class,AdslSupplierPlan::class,'id','id','adsl_supplier_plan_id','supplier_id');
}
public function __get($key)
{
switch($key) {
case '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');
}
}

View File

@@ -0,0 +1,178 @@
<?php
namespace App\Models\Product;
use Illuminate\Support\Collection;
use App\Interfaces\ProductSupplier;
use App\Models\Base\ProductType;
use App\Models\{Product,Supplier};
use App\Models\Service\Broadband as ServiceBroadband;
use App\Models\Supplier\Broadband as SupplierBroadband;
use App\Traits\{OrderServiceOptions,SiteID};
class Broadband extends ProductType implements ProductSupplier
{
use SiteID;
use OrderServiceOptions;
protected $table = 'product_broadband';
// Information required during the order process
private array $order_attributes = [
'options.address'=>[
'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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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
*

View File

@@ -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

View File

@@ -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 */
/**

View File

@@ -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;
}
}

View File

@@ -1,14 +1,112 @@
<?php
namespace App\Models;
namespace App\Models\Supplier;
use App\Traits\ProductDetails;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Leenooks\Traits\ScopeActive;
class AdslSupplierPlan extends Model
use App\Interfaces\SupplierItem;
use App\Models\{Invoice,Supplier,SupplierDetail,Tax};
use App\Models\Product\Broadband as ProductBroadband;
use App\Traits\SiteID;
class Broadband extends Model implements SupplierItem
{
protected $table = 'ab_adsl_supplier_plan';
use SiteID,ScopeActive,ProductDetails;
protected $casts = [
'offpeak_start' => '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;
}

View File

@@ -0,0 +1,7 @@
<?php
namespace App\Models\Supplier;
class Ethernet extends Broadband
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace App\Models\Supplier;
class HSPA extends Broadband
{
}

View File

@@ -10,6 +10,8 @@ class SupplierDetail extends Model
{
use SiteID;
protected $casts = [ 'connections'=>'collection' ];
/* RELATIONS */
public function supplier()

View File

@@ -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);
}
}

View File

@@ -495,7 +495,7 @@ class User extends Authenticatable
}
$result->load([
'product.descriptions',
'product.description',
'service.type',
]);