Move billing charge service::class methods into __get(), no functional changes

This commit is contained in:
Deon George 2025-05-21 15:14:34 +10:00
parent 2b06eca080
commit 4b429cbf20
9 changed files with 49 additions and 81 deletions

View File

@ -23,21 +23,26 @@ use App\Traits\{ScopeAccountUserAuthorised,ScopeServiceActive,SiteID};
* Class Service
* Services that belong to an account
*
* So each service attribute has:
* - Offering, what product we supply (we make offerings from supplier's supplied products) - in the DB these are products/*
* - Supplied, our supplier's product that is providing the service - in the DB these are supplier/*
* - Type, what service we are providing, made up of a product we supply - in the DB these are service/*
* So each service attribute has:
* - Offering, what product we supply (we make offerings from supplier's supplied products) - in the DB these are products/*
* - Supplied, our supplier's product that is providing the service - in the DB these are supplier/*
* - Type, what service we are providing, made up of a product we supply - in the DB these are service/*
*
* Attributes for services:
* + billing_orig : Charge for this service before being overridden by $this->price (ex TAX)
* + billing_orig_normalised_taxed: Charge for this service before being overridden by $this->price, normalised to MONTHLY (with Account TAX)
* + billing_orig_taxed : Charge for this service before being overridden by $this->price (with Account TAX)
* + billing_charge : Charge for this service each invoice period (ex TAX)
* + billing_charge_normalised_taxed: Charge for this service each invoice period, normalised to MONTHLY (with Account TAX)
* + billing_charge_taxed : Charge for this service each invoice period (with Account TAX)
* + billing_interval : The period that this service is billed
* + billing_interval_name : The period that this service is billed as a name
* + is_billed : Does this service generate an invoice
* + is_charge_overridden : Has the price been overridden
* + is_cost_overridden : Has the cost been overridden
*
* Attributes for services (OLD):
* + additional_cost : Pending additional charges for this service (excluding setup) //@todo check all these are still valid
* + billing_charge : Charge for this service each invoice period
* + billing_interval : The period that this service is billed for by default
* + billing_interval_string : The period that this service is billed for by default as a name
* + invoiced_to : When this service has been billed to
* + contract_term : The term that this service must be active
* + contract_end : The date that the contract ends for this service
@ -53,7 +58,6 @@ use App\Traits\{ScopeAccountUserAuthorised,ScopeServiceActive,SiteID};
*
* @package App\Models
* @todo Add min_charge
* @todo Add charge_orig : The original chargeable price
*/
class Service extends Model implements IDs
{
@ -274,6 +278,14 @@ class Service extends Model implements IDs
public function __get($key): mixed
{
return match ($key) {
'billing_orig' => $this->product->getBaseChargeAttribute($this->billing_interval,$this->account->group),
'billing_orig_normalised_taxed' => $this->billing_orig_taxed*Invoice::billing_change($this->billing_interval,Invoice::BILL_MONTHLY),
'billing_orig_taxed' => $this->account->taxed($this->billing_orig),
'billing_charge' => $this->billing_charge(),
'billing_charge_normalised_taxed' => $this->billing_charge_taxed*Invoice::billing_change($this->billing_interval,Invoice::BILL_MONTHLY),
'billing_charge_taxed' => $this->account->taxed($this->billing_charge),
'billing_interval' => $this->recur_schedule ?: $this->product->billing_interval,
'billing_interval_name' => Invoice::billing_name($this->billing_interval),
'is_billed' => (! ($this->external_billing || $this->suspend_billing || ($this->price === 0))),
'is_charge_overridden' => (! is_null($this->price)),
'is_cost_overridden' => (! is_null($this->cost)),
@ -507,62 +519,18 @@ class Service extends Model implements IDs
/* ATTRIBUTES */
/**
* How much do we charge for this service, base on the current recur schedule
* price in the DB overrides the base price used
*
* @return float
*/
public function getBillingChargeAttribute(): float
{
return $this->account->taxed($this->billing_charge());
}
public function getBillingCostAttribute(): float
{
return $this->account->taxed($this->billing_cost());
}
/**
* Determine a monthly price for a service, even if it is billed at a different frequency
*
* @return float
* @throws Exception
*/
public function getBillingChargeNormalisedAttribute(): float
{
return number_format(
$this->getBillingChargeAttribute()*Invoice::billing_change($this->getBillingIntervalAttribute(),$this->offering->billing_interval),
2);
}
public function getBillingCostNormalisedAttribute(): float
{
return number_format(
$this->getBillingCostAttribute()*Invoice::billing_change($this->getBillingIntervalAttribute(),$this->offering->billing_interval),
$this->getBillingCostAttribute()*Invoice::billing_change($this->billing_interval,$this->offering->billing_interval),
2);
}
/**
* 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 getBillingIntervalStringAttribute(): string
{
return Invoice::billing_name($this->getBillingIntervalAttribute());
}
/**
* Return the earliest date that the service can be canceled as per contract/billing intervals
*
@ -899,14 +867,14 @@ class Service extends Model implements IDs
*
* @return float
*/
public function billing_charge(): float
private function billing_charge(): float
{
// If recur_schedule is null, then we only bill this item once
if (is_null($this->getBillingIntervalAttribute()) && $this->getInvoicedToAttribute())
// If recur_schedule is null, and we have already invoiced, then nothing further required
if (is_null($this->billing_interval) && $this->getInvoicedToAttribute())
$this->price = 0;
return is_null($this->price)
? $this->product->getBaseChargeAttribute($this->getBillingIntervalAttribute(),$this->account->group)
? $this->billing_orig
: $this->price;
}
@ -927,12 +895,12 @@ class Service extends Model implements IDs
$max = max($date,$this->getCancelDateAttribute())->clone();
if (! $this->getPaidToAttribute())
return $this->account->taxed($this->getContractTermAttribute()*$this->getBillingChargeNormalisedAttribute());
return $this->getContractTermAttribute()*$this->billing_charge_normalised_taxed;
if ($this->getPaidToAttribute()->lessThan($max)) {
$d = $this->getPaidToAttribute()->diffInDays($max);
return $this->account->taxed($d/30*$this->getBillingChargeNormalisedAttribute());
return $d/30*$this->billing_charge_normalised_taxed;
}
return 0;
@ -967,7 +935,7 @@ class Service extends Model implements IDs
$max = max($date,$this->getCancelDateAttribute())->clone();
if (! $this->getInvoicedToAttribute())
return $this->account->taxed($this->getContractTermAttribute()*$this->getBillingChargeNormalisedAttribute());
return $this->account->taxed($this->getContractTermAttribute()*$this->getBillingCostNormalisedAttribute());
if ($this->getInvoicedToAttribute()->lessThan($max)) {
$d = $this->getInvoicedToAttribute()->diffInDays($max);
@ -1129,7 +1097,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->getBillingIntervalAttribute(),$this->account->group))
&& $this->product->getSetupChargeAttribute($this->billing_interval,$this->account->group))
{
$ii = new InvoiceItem;
@ -1137,7 +1105,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->getBillingIntervalAttribute(),$this->account->group);
$ii->price_base = $this->product->getSetupChargeAttribute($this->billing_interval,$this->account->group);
$ii->start_at = $this->invoice_next;
$ii->stop_at = $this->invoice_next;
$ii->quantity = 1;
@ -1153,14 +1121,14 @@ class Service extends Model implements IDs
while ($invoiced_to <= ($this->stop_at ?: $billdate)) {
$ii = new InvoiceItem;
$period = Invoice::invoice_period($invoiced_to,$this->getBillingIntervalAttribute(),(bool)$this->product->price_recur_strict);
$period = Invoice::invoice_period($invoiced_to,$this->billing_interval,(bool)$this->product->price_recur_strict);
$ii->active = TRUE;
$ii->service_id = $this->id;
$ii->product_id = $this->product_id;
$ii->item_type = InvoiceItem::INVOICEITEM_SERVICE;
$ii->price_base = $this->billing_charge();
$ii->recur_schedule = $this->getBillingIntervalAttribute();
$ii->price_base = $this->billing_charge;
$ii->recur_schedule = $this->billing_interval;
$ii->start_at = $invoiced_to;
$ii->stop_at = ($this->stop_at && ($this->stop_at < Arr::get($period,'end'))) ? $this->stop_at : Arr::get($period,'end');
$ii->quantity = Invoice::invoice_quantity($ii->start_at,$ii->stop_at,$period);

View File

@ -24,7 +24,7 @@
<td>{{ $so->stop_at ? $so->stop_at->format('Y-m-d') : '-' }}</td>
<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,2) }}</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><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>

View File

@ -47,7 +47,7 @@
<td>{{ $oo->registrar_ns }}</td>
<td>@if ($oo->service->is_billed) {{ $oo->service->invoice_next->format('Y-m-d') }}@else - @endif</td>
<td>@if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items()->sum('total'),2) }}@else - @endif</td>
<td>{{ $oo->service->billing_interval_string }}</td>
<td>{{ $oo->service->billing_interval_name }}</td>
</tr>
@endforeach
</tbody>

View File

@ -51,7 +51,7 @@
<td class="text-right">{{ number_format($oo->accounts ?: 0) }}</td>
<td>@if ($oo->service->is_billed) {{ $oo->service->invoice_next->format('Y-m-d') }} @else - @endif</td>
<td>@if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items()->sum('total'),2) }}@else - @endif</td>
<td>{{ $oo->service->billing_interval_string }}</td>
<td>{{ $oo->service->billing_interval_name }}</td>
</tr>
@endforeach
</tbody>

View File

@ -45,7 +45,7 @@
<td>{{ $oo->service->product->supplier->name }}</td>
<td>@if ($oo->service->is_billed) {{ $oo->service->invoice_next->format('Y-m-d') }}@else - @endif</td>
<td>@if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items()->sum('total'),2) }}@else - @endif</td>
<td>{{ $oo->service->billing_interval_string }}</td>
<td>{{ $oo->service->billing_interval_name }}</td>
</tr>
@endforeach
</tbody>

View File

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

View File

@ -43,14 +43,14 @@
@if(($o->active || $o->isPending()) && (! $o->external_billing))
<tr>
<th>Billed</th>
<td>{{ $o->billing_interval_string }}</td>
<td>{{ $o->billing_interval_name }}</td>
</tr>
<tr>
<th>Amount</th>
@if($o->is_charge_overridden)
<td>@if($o->billing_charge < $o->charge_orig)<del>${{ number_format($o->charge_orig,2) }}</del> @endif${{ number_format($o->billing_charge,2) }}</td>
<td>@if($o->billing_charge < $o->billing_orig)<del>${{ number_format($o->billing_orig_taxed,2) }}</del> @endif${{ number_format($o->billing_charge_taxed,2) }}</td>
@else
<td>${{ number_format($o->billing_charge,2) }}</td>
<td>${{ number_format($o->billing_charge_taxed,2) }}</td>
@endif
</tr>
@if($o->isActive() && $o->invoiced_to)

View File

@ -55,11 +55,11 @@
<tr>
<th>Billed</th>
<td>{{ $o->billing_interval_string }}</td>
<td>{{ $o->billing_interval_name }}</td>
<td>{{ $c->type->billing_interval_string }}</td>
<td>&nbsp;</td>
@if($p->exists)
<td>{{ $o->billing_interval_string }}</td>
<td>{{ $o->billing_interval_name }}</td>
<td>{{ $p->type->billing_interval_string }}</td>
<td>&nbsp;</td>
@endif
@ -67,7 +67,7 @@
<tr>
<th>Billing Price</th>
<td @class(['text-danger'=>$o->is_charge_overridden])>${{ number_format($b=$o->billing_charge,2) }}</td>
<td @class(['text-danger'=>$o->is_charge_overridden])>${{ number_format($b=$o->billing_charge_taxed,2) }}</td>
<td @class(['text-danger'=>$o->is_cost_overridden])>${{ number_format($a=$o->billing_cost,2) }}</td>
<td>{!! markup($a,$b) !!}</td>
@if($p->exists)
@ -81,14 +81,14 @@
<th>Monthly Price</th>
<td @class(['text-danger'=>$o->is_charge_overridden])>
@if($o->is_charge_overridden)
<abbr title="${{ number_format($b=$o->account->taxed($c->base_charge)*Invoice::billing_change($o->billing_interval,Invoice::BILL_MONTHLY),2) }}">${{ number_format($b=$o->billing_charge_normalised,2) }}
<abbr title="${{ number_format($b=$o->billing_orig_normalised_taxed,2) }}">${{ number_format($b=$o->billing_charge_normalised_taxed,2) }}
@else
${{ number_format($b=$o->billing_charge_normalised,2) }}
${{ number_format($b=$o->billing_charge_normalised_taxed,2) }}
@endif
</td>
<td @class(['text-danger'=>$o->is_cost_overridden])>
@if($o->is_cost_overridden)
<abbr title="${{ number_format($a=$o->account->taxed($c->base_cost)*Invoice::billing_change($o->billing_interval,Invoice::BILL_MONTHLY),2) }}">${{ number_format($b=$o->billing_cost_normalised,2) }}
<abbr title="${{ number_format($a=$o->account->taxed($c->base_cost)*Invoice::billing_change($c->billing_interval,Invoice::BILL_MONTHLY),2) }}">${{ number_format($b=$o->billing_cost_normalised,2) }}
@else
${{ number_format($a=$o->billing_cost_normalised,2) }}
@endif

View File

@ -48,7 +48,7 @@
<td class="text-right">${{ number_format($a=$oo->sum('base'),2) }}</td>
<td class="text-right">${{ number_format($oo->sum('excess'),2) }}</td>
<td class="text-right">${{ number_format($x=$oo->sum('cost'),2) }}</td>
<td class="text-right">${{ number_format($b=$oo->first()->service->billing_charge_normalised,2) }}</td>
<td class="text-right">${{ number_format($b=$oo->first()->service->billing_charge_normalised_taxed,2) }}</td>
<td class="text-right {{ $b-$a < 0 ? 'text-danger' : '' }}">${{ number_format($b-$a,2) }}</td>
</tr>
@php($cost += $x)
@ -66,7 +66,7 @@
<td class="text-right">${{ number_format($a=$oo->sum('base'),2) }}</td>
<td class="text-right">${{ number_format($oo->sum('excess'),2) }}</td>
<td class="text-right">${{ number_format($x=$oo->sum('cost'),2) }}</td>
<td class="text-right">${{ number_format($b=$oo->first()->service->billing_charge_normalised,2) }}</td>
<td class="text-right">${{ number_format($b=$oo->first()->service->billing_charge_normalised_taxed,2) }}</td>
<td class="text-right {{ $b-$a < 0 ? 'text-danger' : '' }}">${{ number_format($b-$a,2) }}</td>
</tr>
@php($cost += $x)