Work on products, first completed broadband
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user