Move more product::class methods into __get(), no functional changes
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 43s
Create Docker Image / Final Docker Image Manifest (push) Successful in 10s

This commit is contained in:
Deon George 2025-05-22 14:01:17 +10:00
parent 5ef1a27a64
commit a988720340
13 changed files with 84 additions and 167 deletions

View File

@ -68,7 +68,7 @@ class ServiceList extends Command
$this->info(sprintf($header,
$o->lid,
$o->product->category_name,
substr($o->product->getNameAttribute(),0,35),
substr($o->product->name,0,35),
substr($o->name_short,0,40),
$o->active ? 'active' : 'inactive',
$o->status,

View File

@ -10,14 +10,13 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use Intuit\Exceptions\NotTokenException;
use Intuit\Traits\ProviderTokenTrait;
use Leenooks\Traits\ScopeActive;
use App\Casts\CollectionOrNull;
use App\Interfaces\{IDs,ProductItem};
use App\Traits\{ProductDetails,ProviderRef};
use App\Traits\ProviderRef;
/**
* Class Product
@ -33,17 +32,28 @@ use App\Traits\{ProductDetails,ProviderRef};
* Attributes for products:
* + lid : Local ID for product (part number)
* + sid : System ID for product (part number)
* + base_charge : Default billing amount
*
* + billing_interval : Default Billing Interval
* + billing_interval_string: Default Billing Interval in human-readable form
* + base_charge : Default billing amount
* + base_cost : Cost for this service
*
* + billing_interval : Its the max of what we define, or what the supplier bills us at
*
* + category : Type of product supplied
* + category_lc : Category name in lower case
* + category_name : Type of product supplied (Friendly Name for display, not for internal logic)
*
* + contract_term : Contract term for this product
*
* + description : Product description (description.description_full => description_full)
* + min_charge : Minimum charge taking into account billing interval and setup charges
* + has_usage : Does this product instrument usage
*
* + min_cost : Minimum cost for this product
*
* + name : Details of our product (description.description_short => name_detail)
* + pid : Product ID for our Product (description.name => name_short)
* + setup_charge : Charge to setup this product
*
* + setup_cost : Charge by supplier to setup this product
*
* + supplier : Supplier for this offering
*
* Attributes for product types (type - Product/*)
@ -66,7 +76,7 @@ use App\Traits\{ProductDetails,ProviderRef};
*/
class Product extends Model implements IDs
{
use HasFactory,ProductDetails,ScopeActive,ProviderRef,ProviderTokenTrait;
use HasFactory,ScopeActive,ProviderRef,ProviderTokenTrait;
protected $casts = [
'pricing' => CollectionOrNull::class,
@ -77,10 +87,27 @@ class Product extends Model implements IDs
return match ($key) {
'base_cost' => round($this->supplied->base_cost,2)*Invoice::billing_change($this->type->billing_interval,$this->billing_interval) ?: 0,
'billing_interval' => max($this->price_recur_default,$this->type->billing_interval),
'billing_interval_name' => Invoice::billing_name($this->billing_interval),
'category' => $this->supplied->category,
'category_lc' => strtolower($this->category),
'category_name' => $this->supplied->category_name,
'contract_term' => $this->type->contract_term,
'description' => $this->translate->description,
'has_usage' => $this->type->hasUsage(),
'min_cost' => $this->supplied->min_cost,
'name' => $this->translate->name_detail,
'pid' => $this->translate->name_short,
'setup_cost' => $this->supplied->setup_cost ?: 0,
default => parent::__get($key),
};
}
@ -174,80 +201,6 @@ class Product extends Model implements IDs
/* ATTRIBUTES */
/**
* 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->billing_interval);
}
/**
* How long must this product be purchased for as a service.
*
* @return int
*/
public function getContractTermAttribute(): int
{
return $this->type->contract_term;
}
/**
* This product full description
*
* @return string
*/
public function getDescriptionAttribute(): string
{
return $this->translate->description;
}
/**
* Get the minimum charge for 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->base_charge($timeperiod,$go)*Invoice::billing_change($this->billing_interval,$this->type->billing_interval)*$this->type->contract_term;
}
/**
* Get the minimum cost for this product
*
* @return float
*/
public function getMinCostAttribute(): float
{
return $this->supplied->min_cost;
}
/**
* Our products short descriptive name
*
* @return string
*/
public function getNameAttribute(): string
{
return $this->translate->name_detail;
}
/**
* Our products PID
*
* @return string
*/
public function getPIDAttribute(): string
{
return $this->translate->name_short;
}
/**
* Suppliers product
*
@ -268,28 +221,6 @@ class Product extends Model implements IDs
return $this->supplied->supplier;
}
/**
* 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->_charge('setup',$timeperiod,$go);
}
/**
* The cost to setup this service
*
* @return float
*/
public function getSetupCostAttribute(): float
{
return $this->supplied->setup_cost ?: 0;
}
/* METHODS */
/**
@ -387,24 +318,16 @@ class Product extends Model implements IDs
}
/**
* Return a normalize price dependent on the product, ie: Broadband = Monthly, Domain = Yearly, etc
* Get the minimum charge for this product
*
* @note: By definition products are normalised, as their cost price is based on the default billing interval
* @param int|null $timeperiod
* @param Group|null $go
* @return float
*/
public function cost_normalized(): float
public function min_charge(int $timeperiod=NULL,Group $go=NULL): float
{
return number_format(config('site')->taxed($this->supplied->base_cost),2);
}
/**
* Return if this product captures usage data
*
* @return bool
*/
public function hasUsage(): bool
{
return $this->type->hasUsage();
return $this->setup_charge($timeperiod,$go)
+ $this->base_charge($timeperiod,$go)*Invoice::billing_change($this->billing_interval,$this->type->billing_interval)*$this->contract_term;
}
/**
@ -417,4 +340,16 @@ class Product extends Model implements IDs
{
return $this->type->orderValidation($request);
}
/**
* The charge to setup this service
*
* @param int|null $timeperiod
* @param Group|null $go
* @return float
*/
public function setup_charge(?int $timeperiod=NULL,?Group $go=NULL): float
{
return $this->_charge('setup',$timeperiod,$go);
}
}

View File

@ -10,7 +10,7 @@ use App\Traits\{OrderServiceOptions,ProductDetails,SiteID};
abstract class Type extends Model
{
use SiteID,ProductDetails,OrderServiceOptions;
use SiteID,OrderServiceOptions;
/* RELATIONS */

View File

@ -301,7 +301,7 @@ class Service extends Model implements IDs
'billing_interval' => $this->recur_schedule ?: $this->product->billing_interval,
'billing_interval_name' => Invoice::billing_name($this->billing_interval),
'contract_term' => max($this->supplied->contract_term,$this->product->type->contract_term),
'contract_term' => max($this->product->contract_term,$this->supplied->contract_term),
'is_active' => $this->active || ($this->order_status && (! in_array($this->order_status,self::INACTIVE_STATUS))),
'is_billed' => (! ($this->external_billing || $this->suspend_billing || ($this->price === 0))),
@ -1058,7 +1058,7 @@ class Service extends Model implements IDs
// Connection charges are only charged once, so ignore if if we have already billed them
if ((! $this->invoiced_items()->where('item_type',InvoiceItem::INVOICEITEM_SETUP)->count())
&& (InvoiceItem::distinct('invoice_id')->where('service_id',$this->id)->count() < 2)
&& $this->product->getSetupChargeAttribute($this->billing_interval,$this->account->group))
&& $this->product->setup_charge($this->billing_interval,$this->account->group))
{
$ii = new InvoiceItem;
@ -1066,7 +1066,7 @@ class Service extends Model implements IDs
$ii->service_id = $this->id;
$ii->product_id = $this->product_id;
$ii->item_type = InvoiceItem::INVOICEITEM_SETUP;
$ii->price_base = $this->product->getSetupChargeAttribute($this->billing_interval,$this->account->group);
$ii->price_base = $this->product->setup_charge($this->billing_interval,$this->account->group);
$ii->start_at = $this->invoice_next;
$ii->stop_at = $this->invoice_next;
$ii->quantity = 1;

View File

@ -28,6 +28,7 @@ abstract class Type extends Model
{
return match ($key) {
'category' => (new \ReflectionClass($this))->getShortName(),
'category_lc' => strtolower($this->category),
'category_name' => static::category_name ?: $this->category,
default => parent::__get($key),

View File

@ -1,21 +0,0 @@
<?php
/**
* Consistent Details on Products
*/
namespace App\Traits;
use App\Models\Invoice;
trait ProductDetails
{
/**
* Return a human friendly name for the billing interval
*
* @return string
*/
public function getBillingIntervalStringAttribute(): string
{
return Invoice::billing_name(static::getBillingIntervalAttribute());
}
}

View File

@ -59,10 +59,12 @@
<span class="h5">Accounting</span>
<hr>
<!-- @todo When returning with a bad value old() is not selecting the previous value, may need to have the full html here instead of a component -->
@foreach (ProviderOauth::providers() as $apo)
<x-leenooks::form.select :id="sprintf('acc_%d',$apo->id)" :name="sprintf('accounting[%d]',$apo->id)" icon="fa-calculator" :label="ucfirst($apo->name)" :choose="true" :value="$pdo->provider_ref($apo)" old="accounting" :options="$pdo->accounting($apo->name)"/>
@endforeach
@production
<!-- @todo When returning with a bad value old() is not selecting the previous value, may need to have the full html here instead of a component -->
@foreach (ProviderOauth::providers() as $apo)
<x-leenooks::form.select :id="sprintf('acc_%d',$apo->id)" :name="sprintf('accounting[%d]',$apo->id)" icon="fa-calculator" :label="ucfirst($apo->name)" :choose="true" :value="$pdo->provider_ref($apo)" old="accounting" :options="$pdo->accounting($apo->name)"/>
@endforeach
@endproduction
</div>
</div>
</div>

View File

@ -25,7 +25,7 @@
<td>{{ $so->invoice_to ? $so->invoice_to->format('Y-m-d') : '-' }}</td>
<td>{{ $so->active ? 'YES' : 'NO' }}</td>
<td class="text-right">{{ $a=number_format($so->billing_charge_normalised_taxed,2) }}</td>
<td class="text-right">{{ $b=number_format($so->product->cost_normalized(),2) }}</td>
<td class="text-right">{{ $b=number_format($so->billing_cost_normalised_taxed,2) }}</td>
<td><button class="btn btn-sm @if($a<$b)btn-danger @else btn-success @endif"><small>@if($a<$b)<i class="fas fa-fw fa-exclamation"></i> @else <i class="fas fa-fw fa-check"></i> @endif</small></button></td>
</tr>
@endforeach

View File

@ -32,7 +32,7 @@
<li class="nav-item"><a @class(['nav-link','active'=>! (session()->has('service_update') || session()->has('charge_add'))]) href="#pending_items" data-toggle="tab">Pending Items</a></li>
@endif
@if($o->product->hasUsage())
@if($o->product->has_usage)
<li class="nav-item"><a @class(['nav-link','active'=>! ($x || (session()->has('service_update') || session()->has('charge_add')))]) href="#traffic" data-toggle="tab">Traffic</a></li>
@endif
@ -70,7 +70,7 @@
</div>
@endif
@if($o->product->hasUsage())
@if($o->product->has_usage)
<div @class(['tab-pane','fade','show active'=>! ($x || (session()->has('service_update') || session()->has('charge_add')))]) id="traffic">
@if($o->type->usage(30)->count())
@include('theme.backend.adminlte.service.widget.'.$o->product->category_lc.'.usagegraph',['o'=>$o->type])

View File

@ -42,7 +42,7 @@
<td>{{ $o->supplierid }}</td>
<td class="text-right">{{ number_format($o->billing_charge_normalised_taxed,2) }}</td>
<td class="text-right">{{ number_format($o->billing_cost_normalised_taxed,2) }}</td>
<td class="text-right">{{ $o->product->hasUsage() ? number_format($o->type->usage_summary(0)->sum()/1000,1) : '-' }}</td>
<td class="text-right">{{ $o->product->has_usage ? number_format($o->type->usage_summary(0)->sum()/1000,1) : '-' }}</td>
<td>{{ $o->product->supplier->name }}</td>
</tr>
@endforeach

View File

@ -45,11 +45,11 @@
<tr>
<th>Setup</th>
<td>${{ number_format($b=$o->account->taxed($c->setup_charge),2) }}</td>
<td>${{ number_format($b=$o->account->taxed($c->setup_charge()),2) }}</td>
<td>${{ number_format($a=$o->account->taxed($c->setup_cost),2) }}</td>
<td>{!! markup($a,$b) !!}</td>
@if($p->exists)
<td>${{ number_format($b=$o->account->taxed($p->setup_charge),2) }}</td>
<td>${{ number_format($b=$o->account->taxed($p->setup_charge()),2) }}</td>
<td>${{ number_format($a=$o->account->taxed($p->setup_cost),2) }}</td>
<td>{!! markup($a,$b) !!}</td>
@endif
@ -58,11 +58,11 @@
<tr>
<th>Billed</th>
<td>{{ $o->billing_interval_name }}</td>
<td>{{ $c->type->billing_interval_string }}</td>
<td>{{ $c->billing_interval_name }}</td>
<td>&nbsp;</td>
@if($p->exists)
<td>{{ $o->billing_interval_name }}</td>
<td>{{ $p->type->billing_interval_string }}</td>
<td>{{ $p->billing_interval_name }}</td>
<td>&nbsp;</td>
@endif
</tr>
@ -118,13 +118,13 @@
<tr>
<th>Min Price</th>
<!-- @todo change this to $o->min_charge when implemented -->
<td>${{ number_format($b=$o->account->taxed($o->product->min_charge),2) }}</td>
<td>${{ number_format($a=$o->account->taxed($c->supplied->min_cost),2) }}</td>
<td>${{ number_format($b=$o->account->taxed($o->product->min_charge()),2) }}</td>
<td>${{ number_format($a=$o->account->taxed($c->min_cost),2) }}</td>
<td>{!! markup($a,$b) !!}</td>
@if($p->exists)
<td>${{ number_format($a=$o->account->taxed($p->min_charge),2) }}</td>
<td>${{ number_format($a=$o->account->taxed($p->supplied->min_cost ?? 0),2) }}</td>
<td>${{ number_format($a=$o->account->taxed($p->min_charge()),2) }}</td>
<td>${{ number_format($a=$o->account->taxed($p->min_cost),2) }}</td>
<td>{!! markup($a,$b) !!}</td>
@endif
</tr>

View File

@ -40,13 +40,13 @@
@foreach($oo->products->pluck('products')->flatten()->filter() as $po)
<tr>
<td><a href="{{ route('product',['pdo'=>$po->id]) }}">{{ $po->lid }}</a></td>
<td>{{ $po->pid }} <small>[<a href="{{ route('supplier.product.type',['id'=>$po->supplied->id,'spo'=>$po->supplier->id,'type'=>$po->supplied->category_lc]) }}">{{ $po->supplied->name }}</a>]</small></td>
<td>{{ $po->pid }} <small>[<a href="{{ route('supplier.product.type',['id'=>$po->supplied->id,'spo'=>$po->supplier->id,'type'=>$po->category_lc]) }}">{{ $po->supplied->name }}</a>]</small></td>
<td>{{ $po->name }}</td>
<td class="text-right">{{ $po->active ? 'YES' : 'NO' }}</td>
<td class="text-right">{{ $po->billing_interval_string }}</td>
<td class="text-right">{{ $po->billing_interval_name }}</td>
<td class="text-right">{{ number_format($site->taxed($po->setup_cost),2) }}</td>
<td class="text-right">{{ number_format($site->taxed($po->base_cost),2) }}</td>
<td class="text-right">{{ number_format($site->taxed($po->setup_charge),2) }}</td>
<td class="text-right">{{ number_format($site->taxed($po->setup_charge()),2) }}</td>
<td class="text-right">{{ number_format($site->taxed($po->base_charge()),2) }}</td>
<td class="text-right">{{ number_format($po->services->count()) }}</td>
<td class="text-right">{{ number_format($po->services->where('active')->count()) }}</td>

View File

@ -10,11 +10,11 @@
<th>Type</th>
<td class="text-right">{{ $pdo->category_name }}</td>
</tr>
@if ($pdo->setup_charge)
@if ($pdo->setup_charge())
<tr>
<th>Setup Charges <sup>*</sup></th>
{{-- @todo this should use account::taxed() when the user is known --}}
<td class="text-right">${{ number_format($user->exists ? Config::get('site')->taxed($pdo->setup_charge) : Config::get('site')->taxed($pdo->setup_charge),2) }}</td>
<td class="text-right">${{ number_format($user->exists ? Config::get('site')->taxed($pdo->setup_charge()) : Config::get('site')->taxed($pdo->setup_charge()),2) }}</td>
</tr>
@endif
<tr>
@ -24,7 +24,7 @@
</tr>
<tr>
<th>Default Billing</th>
<td class="text-right">{{ $pdo->billing_interval_string }}</td>
<td class="text-right">{{ $pdo->billing_interval_name }}</td>
</tr>
<tr>
<th>Contract Term</th>
@ -33,7 +33,7 @@
<tr>
<th>Minimum Costs <sup>+*</sup></th>
{{-- @todo this should use account::taxed() when the user is known --}}
<td class="text-right">${{ number_format($user->exists ? Config::get('site')->taxed($pdo->min_charge) : Config::get('site')->taxed($pdo->min_charge),2) }}</td>
<td class="text-right">${{ number_format($user->exists ? Config::get('site')->taxed($pdo->min_charge()) : Config::get('site')->taxed($pdo->min_charge()),2) }}</td>
</tr>
<tfoot>