Updates to Product Model, product updates, enable pricing update, improved formating of product services
This commit is contained in:
@@ -4,8 +4,19 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Leenooks\Traits\ScopeActive;
|
||||
|
||||
class Group extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use HasFactory, ScopeActive;
|
||||
|
||||
/* SCOPES */
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function scopePricing()
|
||||
{
|
||||
return $this->where('pricing',TRUE);
|
||||
}
|
||||
}
|
||||
|
@@ -11,7 +11,6 @@ use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Leenooks\Traits\ScopeActive;
|
||||
|
||||
use App\Interfaces\{IDs,ProductItem};
|
||||
@@ -24,12 +23,15 @@ use App\Traits\{ProductDetails,SiteID};
|
||||
* 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.
|
||||
*
|
||||
* So each product attribute has:
|
||||
* + supplied : Supplier product provided for this offering (Supplier/*)
|
||||
* + type : Returns the underlying product object, representing the type of product (Product/*)
|
||||
*
|
||||
* Attributes for products:
|
||||
* + lid : Local ID for product (part number)
|
||||
* + sid : System ID for product (part number)
|
||||
* + category : Type of product supplied
|
||||
* + category_name : Type of product supplied (Friendly Name for display, not for internal logic)
|
||||
* + supplied : Supplier product provided for this offering
|
||||
* + supplier : Supplier for this offering
|
||||
* + name : Brief Name for our product with name_detail
|
||||
* + name_short : Product ID for our Product (description.name => name_short)
|
||||
@@ -41,9 +43,8 @@ use App\Traits\{ProductDetails,SiteID};
|
||||
* + 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
|
||||
* + type : Returns the underlying product object, representing the type of product
|
||||
* + min_charge : Minimum charge taking into account billing interval and setup charges
|
||||
* + min_charge_taxable : Minimum charge taking into account billing interval and setup charges including taxes
|
||||
*
|
||||
* Attributes for product types (type - Product/*)
|
||||
* + name : Short Name for our Product
|
||||
@@ -74,7 +75,37 @@ class Product extends Model implements IDs
|
||||
'pricing'=>'collection',
|
||||
];
|
||||
|
||||
protected $with = ['translate'];
|
||||
/* STATIC */
|
||||
|
||||
/**
|
||||
* Return a list of available product types
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public static function availableTypes(): Collection
|
||||
{
|
||||
$models = collect(File::allFiles(app_path()))
|
||||
->map(function ($item) {
|
||||
$path = $item->getRelativePathName();
|
||||
$class = sprintf('%s%s',
|
||||
Container::getInstance()->getNamespace(),
|
||||
strtr(substr($path, 0, strrpos($path, '.')), '/', '\\'));
|
||||
|
||||
return $class;
|
||||
})
|
||||
->filter(function ($class) {
|
||||
$valid = FALSE;
|
||||
|
||||
if (class_exists($class)) {
|
||||
$reflection = new \ReflectionClass($class);
|
||||
$valid = $reflection->isSubclassOf(ProductItem::class) && (! $reflection->isAbstract());
|
||||
}
|
||||
|
||||
return $valid;
|
||||
});
|
||||
|
||||
return $models->values();
|
||||
}
|
||||
|
||||
/* RELATIONS */
|
||||
|
||||
@@ -349,33 +380,38 @@ class Product extends Model implements IDs
|
||||
/* METHODS */
|
||||
|
||||
/**
|
||||
* Return a list of available product types
|
||||
* Return the charge from the pricing table for the specific time period and group
|
||||
*
|
||||
* @return Collection
|
||||
* @param int $timeperiod
|
||||
* @param Group $go
|
||||
* @param string $type
|
||||
* @return float|null
|
||||
*/
|
||||
function availableTypes(): Collection
|
||||
public function charge(int $timeperiod,Group $go,string $type): ?float
|
||||
{
|
||||
$models = collect(File::allFiles(app_path()))
|
||||
->map(function ($item) {
|
||||
$path = $item->getRelativePathName();
|
||||
$class = sprintf('%s%s',
|
||||
Container::getInstance()->getNamespace(),
|
||||
strtr(substr($path, 0, strrpos($path, '.')), '/', '\\'));
|
||||
return Arr::get($this->pricing,sprintf('%d.%d.%s',$timeperiod,$go->id,$type));
|
||||
}
|
||||
|
||||
return $class;
|
||||
})
|
||||
->filter(function ($class) {
|
||||
$valid = FALSE;
|
||||
/**
|
||||
* Do we have a charge for specific group/period
|
||||
*
|
||||
* @param int $timeperiod
|
||||
* @return bool
|
||||
*/
|
||||
public function charge_available(int $timeperiod): bool
|
||||
{
|
||||
return Arr::get($this->pricing,sprintf('%d.show',$timeperiod),FALSE);
|
||||
}
|
||||
|
||||
if (class_exists($class)) {
|
||||
$reflection = new \ReflectionClass($class);
|
||||
$valid = $reflection->isSubclassOf(ProductItem::class) && (! $reflection->isAbstract());
|
||||
}
|
||||
|
||||
return $valid;
|
||||
});
|
||||
|
||||
return $models->values();
|
||||
/**
|
||||
* Return a normalize price dependent on the product, ie: Broadband = Monthly, Domain = Yearly, etc
|
||||
*
|
||||
* @note: By definition products are normalised, as their cost price is based on the default billing interval
|
||||
* @return float
|
||||
*/
|
||||
public function cost_normalized(): float
|
||||
{
|
||||
return number_format(Tax::tax_calc($this->supplied->base_cost,config('site')->taxes),2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -385,6 +421,7 @@ class Product extends Model implements IDs
|
||||
* @param int|NULL $timeperiod
|
||||
* @param Group|NULL $go
|
||||
* @return float
|
||||
* @todo use self::charge()
|
||||
*/
|
||||
private function getCharge(string $type,int $timeperiod=NULL,Group $go=NULL): float
|
||||
{
|
||||
@@ -407,16 +444,9 @@ class Product extends Model implements IDs
|
||||
$alt_tp--;
|
||||
}
|
||||
|
||||
if (! is_null($price) && $alt_tp !== $timeperiod) {
|
||||
// If we havent got a price, we'll extrapolate one, except for setup charges
|
||||
if (! is_null($price) && ($alt_tp !== $timeperiod) && ($type !== 'setup'))
|
||||
$price = $price*Invoice::billing_change($alt_tp,$timeperiod);
|
||||
}
|
||||
}
|
||||
|
||||
// @todo - if price doesnt exist for the time period, reduce down to timeperiod 1 and multiply appropriately.
|
||||
if (is_null($price)) {
|
||||
Log::error(sprintf('Price is still null for [%d] timeperiod [%d] group [%d]',$this->id,$timeperiod,$go->id));
|
||||
|
||||
$price = 0;
|
||||
}
|
||||
|
||||
return round($price,2);
|
||||
|
@@ -6,6 +6,7 @@ use Illuminate\Support\Collection;
|
||||
use Leenooks\Traits\ScopeActive;
|
||||
|
||||
use App\Interfaces\ProductItem;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Service\Broadband as ServiceBroadband;
|
||||
use App\Models\Supplier\Broadband as SupplierBroadband;
|
||||
|
||||
@@ -34,6 +35,8 @@ final class Broadband extends Type implements ProductItem
|
||||
// The model that is referenced when this product is ordered
|
||||
protected string $order_model = ServiceBroadband::class;
|
||||
|
||||
// When comparing billing/pricing/charging, what metric to normalise to
|
||||
const DefaultBill = Invoice::BILL_MONTHLY;
|
||||
// The model that the supplier supplies
|
||||
const SupplierModel = SupplierBroadband::class;
|
||||
|
||||
|
@@ -5,6 +5,7 @@ namespace App\Models\Product;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Interfaces\ProductItem;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Service\Domain as ServiceDomain;
|
||||
use App\Models\Supplier\Domain as SupplierDomain;
|
||||
|
||||
@@ -31,6 +32,8 @@ final class Domain extends Type implements ProductItem
|
||||
// The model that is referenced when this product is ordered
|
||||
protected string $order_model = ServiceDomain::class;
|
||||
|
||||
// When comparing billing/pricing/charging, what metric to normalise to
|
||||
const DefaultBill = Invoice::BILL_YEARLY;
|
||||
// The model that the supplier supplies
|
||||
const SupplierModel = SupplierDomain::class;
|
||||
|
||||
|
@@ -5,6 +5,7 @@ namespace App\Models\Product;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Interfaces\ProductItem;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Service\Email as ServiceEmail;
|
||||
use App\Models\Supplier\Email as SupplierEmail;
|
||||
|
||||
@@ -15,6 +16,8 @@ final class Email extends Type implements ProductItem
|
||||
// The model that is referenced when this product is ordered
|
||||
protected string $order_model = ServiceEmail::class;
|
||||
|
||||
// When comparing billing/pricing/charging, what metric to normalise to
|
||||
const DefaultBill = Invoice::BILL_YEARLY;
|
||||
// The model that the supplier supplies
|
||||
const SupplierModel = SupplierEmail::class;
|
||||
|
||||
|
@@ -5,6 +5,7 @@ namespace App\Models\Product;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Interfaces\ProductItem;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Service\Generic as ServiceGeneric;
|
||||
use App\Models\Supplier\Generic as SupplierGeneric;
|
||||
|
||||
@@ -15,6 +16,8 @@ final class Generic extends Type implements ProductItem
|
||||
// The model that is referenced when this product is ordered
|
||||
protected string $order_model = ServiceGeneric::class;
|
||||
|
||||
// When comparing billing/pricing/charging, what metric to normalise to
|
||||
const DefaultBill = Invoice::BILL_MONTHLY;
|
||||
// The model that the supplier supplies
|
||||
const SupplierModel = SupplierGeneric::class;
|
||||
|
||||
|
@@ -5,6 +5,7 @@ namespace App\Models\Product;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Interfaces\ProductItem;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Service\Host as ServiceHost;
|
||||
use App\Models\Supplier\Host as SupplierHost;
|
||||
|
||||
@@ -15,6 +16,8 @@ final class Host extends Type implements ProductItem
|
||||
// The model that is referenced when this product is ordered
|
||||
protected string $order_model = ServiceHost::class;
|
||||
|
||||
// When comparing billing/pricing/charging, what metric to normalise to
|
||||
const DefaultBill = Invoice::BILL_MONTHLY;
|
||||
// The model that the supplier supplies
|
||||
const SupplierModel = SupplierHost::class;
|
||||
|
||||
|
@@ -5,6 +5,7 @@ namespace App\Models\Product;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Interfaces\ProductItem;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Service\Phone as ServicePhone;
|
||||
use App\Models\Supplier\Phone as SupplierPhone;
|
||||
|
||||
@@ -42,6 +43,8 @@ final class Phone extends Type implements ProductItem
|
||||
// The model that is referenced when this product is ordered
|
||||
protected string $order_model = ServicePhone::class;
|
||||
|
||||
// When comparing billing/pricing/charging, what metric to normalise to
|
||||
const DefaultBill = Invoice::BILL_MONTHLY;
|
||||
// The model that the supplier supplies
|
||||
const SupplierModel = SupplierPhone::class;
|
||||
|
||||
|
@@ -5,6 +5,7 @@ namespace App\Models\Product;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
use App\Interfaces\ProductItem;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Service\SSL as ServiceSSL;
|
||||
use App\Models\Supplier\SSL as SupplierSSL;
|
||||
|
||||
@@ -15,6 +16,8 @@ final class SSL extends Type implements ProductItem
|
||||
// The model that is referenced when this product is ordered
|
||||
protected string $order_model = ServiceSSL::class;
|
||||
|
||||
// When comparing billing/pricing/charging, what metric to normalise to
|
||||
const DefaultBill = Invoice::BILL_MONTHLY;
|
||||
// The model that the supplier supplies
|
||||
const SupplierModel = SupplierSSL::class;
|
||||
|
||||
|
@@ -22,7 +22,7 @@ abstract class Type extends Model
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
|
||||
*/
|
||||
public function products()
|
||||
final public function products()
|
||||
{
|
||||
return $this->morphMany(Product::class, null,'model','model_id');
|
||||
}
|
||||
@@ -32,7 +32,7 @@ abstract class Type extends Model
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function supplied()
|
||||
final public function supplied()
|
||||
{
|
||||
return $this->hasOne(static::SupplierModel,'id','supplier_item_id');
|
||||
}
|
||||
@@ -61,4 +61,11 @@ abstract class Type extends Model
|
||||
abort(500,'use product->supplied->category_name');
|
||||
return static::category_name;
|
||||
}
|
||||
|
||||
/* METHODs */
|
||||
|
||||
final function normalizeBillingInterval(): int
|
||||
{
|
||||
return static::DefaultBill;
|
||||
}
|
||||
}
|
@@ -18,6 +18,7 @@ use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Leenooks\Carbon as LeenooksCarbon;
|
||||
|
||||
use App\Models\Product\Type;
|
||||
use App\Models\Scopes\SiteScope;
|
||||
use App\Interfaces\IDs;
|
||||
use App\Traits\ScopeServiceUserAuthorised;
|
||||
@@ -530,6 +531,7 @@ class Service extends Model implements IDs
|
||||
*
|
||||
* @return float
|
||||
* @throws Exception
|
||||
* @deprecated use class::charge_normalized()
|
||||
*/
|
||||
public function getBillingMonthlyPriceAttribute(): float
|
||||
{
|
||||
@@ -809,7 +811,7 @@ class Service extends Model implements IDs
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function getOfferingAttribute(): Model
|
||||
public function getOfferingAttribute(): Type
|
||||
{
|
||||
return $this->product->type;
|
||||
}
|
||||
@@ -1068,6 +1070,16 @@ class Service extends Model implements IDs
|
||||
return round($value*1.1,2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a normalize price dependent on the product, ie: Broadband = Monthly, Domain = Yearly, etc
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function charge_normalized(): float
|
||||
{
|
||||
return number_format($this->getBillingChargeAttribute()*Invoice::billing_change($this->recur_schedule,$this->offering->normalizeBillingInterval()),2);
|
||||
}
|
||||
|
||||
private function getOrderInfoValue(string $key): ?string
|
||||
{
|
||||
return $this->order_info ? $this->order_info->get($key) : NULL;
|
||||
|
@@ -37,6 +37,10 @@ class Broadband extends Type implements SupplierItem
|
||||
|
||||
/* INTERFACES */
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @deprecated use Product::normalizeBillingInterval()
|
||||
*/
|
||||
public function getBillingIntervalAttribute(): int
|
||||
{
|
||||
return 1; // Monthly
|
||||
|
Reference in New Issue
Block a user