Compare commits

..

No commits in common. "c4809a311d287116be7d16f0232766dbbb01c4df" and "98d18c00a3a20866e7f9ecff2dfc13f0a0d69e7c" have entirely different histories.

22 changed files with 309 additions and 172 deletions

View File

@ -49,7 +49,7 @@ class ServiceList extends Command
)); ));
foreach (Service::cursor() as $o) { foreach (Service::cursor() as $o) {
if ((! $this->option('inactive')) && (! $o->is_active)) if ((! $this->option('inactive')) && (! $o->isActive()))
continue; continue;
if ($this->option('type') && ($o->product->getCategoryAttribute() !== $this->option('type'))) if ($this->option('type') && ($o->product->getCategoryAttribute() !== $this->option('type')))

View File

@ -257,7 +257,7 @@ class Account extends Model implements IDs
{ {
$svs = $this $svs = $this
->services_active ->services_active
->filter(fn($item)=>$item->is_billed && $item->invoice_next) ->filter(fn($item)=>$item->isBilled() && $item->invoice_next)
->sortBy(fn($item)=>(string)$item->invoice_next); ->sortBy(fn($item)=>(string)$item->invoice_next);
// Collect all the invoice items for our active services // Collect all the invoice items for our active services
@ -280,7 +280,7 @@ class Account extends Model implements IDs
->flatten(); ->flatten();
// Add any account charges (charges with no active service) // Add any account charges (charges with no active service)
foreach ($this->charges->filter(function($item) { return $item->unprocessed && ((! $this->service_id) || (! $item->service->is_billed)); }) as $oo) { foreach ($this->charges->filter(function($item) { return $item->unprocessed && ((! $this->service_id) || (! $item->service->isBilled())); }) as $oo) {
$ii = new InvoiceItem; $ii = new InvoiceItem;
$ii->active = TRUE; $ii->active = TRUE;

View File

@ -29,34 +29,12 @@ use App\Traits\{ScopeAccountUserAuthorised,ScopeServiceActive,SiteID};
* - Type, what service we are providing, made up of a product we supply - in the DB these are service/* * - Type, what service we are providing, made up of a product we supply - in the DB these are service/*
* *
* Attributes for services: * Attributes for services:
* + billing_charge : Charge for this service each invoice period (ex TAX) * + additional_cost : Pending additional charges for this service (excluding setup) //@todo check all these are still valid
* + billing_charge_normalised_taxed: Charge for this service each invoice period, normalised to MONTHLY (with Account TAX) * + billing_charge : Charge for this service each invoice period
* + billing_charge_taxed : Charge for this service each invoice period (with Account TAX) * + billing_interval : The period that this service is billed for by default
* + billing_charge_orig : Charge for this service before being overridden by $this->price (ex TAX) * + billing_interval_string : The period that this service is billed for by default as a name
* + billing_charge_orig_normalised_taxed: Charge for this service before being overridden by $this->price, normalised to MONTHLY (with Account TAX)
* + billing_charge_orig_taxed : Charge for this service before being overridden by $this->price (with Account TAX)
* + billing_cost : Cost for this service each invoice period (ex TAX)
* + billing_cost_normalised_taxed: Cost for this service each invoice period, normalised to MONTHLY (with Account TAX)
* + billing_cost_taxed : Cost for this service each invoice period (with Account TAX)
* + billing_cost_orig : Cost for this service before being overridden by $this->cost (ex TAX)
* + billing_cost_orig_normalised_taxed: Cost for this service before being overridden by $this->cost, normalised to MONTHLY (with Account TAX)
* + billing_cost_orig_taxed : Cost for this service before being overridden by $this->cost (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
* + contract_term : The term that this service must be active
* + is_active : Is this service active. It is active, if active=true, or the order_status is not in self::INACTIVE_STATUS[]
* + is_billed : Does this service generate an invoice
* + is_cancelled : Service that has been cancelled or never provisioned
* + is_charge_overridden : Has the price been overridden
* + is_contracted : Is this service on a contract
* + is_cost_overridden : Has the cost been overridden
* + is_pending_active : Has this service been ordered, waiting to be active
* + is_pending_change : Is this service changing
* + is_pending_cancel : Is this active service being cancelled
* + status : Service status (inactive/active)
*
* Attributes for services (OLD):
* + invoiced_to : When this service has been billed to * + 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 * + contract_end : The date that the contract ends for this service
* + name : Service short name with service address * + name : Service short name with service address
* + name_short : Service Product short name, eg: phone number, domain name, certificate CN * + name_short : Service Product short name, eg: phone number, domain name, certificate CN
@ -64,6 +42,15 @@ use App\Traits\{ScopeAccountUserAuthorised,ScopeServiceActive,SiteID};
* + product : Our product that is providing this service * + product : Our product that is providing this service
* + sid : System ID for service * + sid : System ID for service
* + supplied : The model of the supplier's product used for this service. * + supplied : The model of the supplier's product used for this service.
*
* Methods:
* + isChargeOverridden : Has the price been overridden?
* + isCostOverridden : Has the cost been overridden?
* + isPending : Is this a pending active service
*
* @package App\Models
* @todo Add min_charge
* @todo Add charge_orig : The original chargeable price
*/ */
class Service extends Model implements IDs class Service extends Model implements IDs
{ {
@ -281,46 +268,6 @@ class Service extends Model implements IDs
], ],
]; ];
public function __get($key): mixed
{
return match ($key) {
'billing_cost' => $this->billing_cost(),
'billing_cost_normalised_taxed' => $this->billing_cost_taxed*Invoice::billing_change($this->billing_interval,Invoice::BILL_MONTHLY),
'billing_cost_taxed' => $this->account->taxed($this->billing_cost),
'billing_cost_orig' => $this->product->base_cost,
'billing_cost_orig_normalised_taxed' => $this->billing_cost_orig_taxed*Invoice::billing_change($this->product->type->billing_interval,Invoice::BILL_MONTHLY),
'billing_cost_orig_taxed' => $this->account->taxed($this->billing_cost_orig),
'billing_charge_orig' => $this->product->getBaseChargeAttribute($this->billing_interval,$this->account->group),
'billing_charge_orig_normalised_taxed' => $this->billing_charge_orig_taxed*Invoice::billing_change($this->billing_interval,Invoice::BILL_MONTHLY),
'billing_charge_orig_taxed' => $this->account->taxed($this->billing_charge_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),
'contract_term' => max($this->supplied->contract_term,$this->product->type->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))),
'is_cancelled' => in_array($this->order_status,self::INACTIVE_STATUS),
'is_charge_overridden' => (! is_null($this->price)),
'is_contracted' => $this->getContractEndAttribute() && $this->getContractEndAttribute()->greaterThan(Carbon::now()),
'is_cost_overridden' => (! is_null($this->cost)),
'is_pending_active' => (! $this->active) && (! is_null($this->order_status)) && (! in_array($this->order_status,array_merge(self::INACTIVE_STATUS,['INACTIVE']))),
'is_pending_change' => $this->active && $this->changes()->where('service__change.active',TRUE)->where('complete',FALSE)->count(),
'is_pending_cancel' => $this->active && in_array(strtolower($this->order_status),['cancel-request','cancel-pending']),
'status' => $this->active
? strtolower($this->order_status)
: ((strtolower($this->order_status) === 'cancelled') ? 'cancelled' : 'inactive'),
default => parent::__get($key),
};
}
/* STATIC */ /* STATIC */
/** /**
@ -348,7 +295,7 @@ class Service extends Model implements IDs
* *
* @return string * @return string
*/ */
public function getLIDAttribute(): string public function getLIDattribute(): string
{ {
return sprintf('%05s',$this->id); return sprintf('%05s',$this->id);
} }
@ -517,6 +464,37 @@ class Service extends Model implements IDs
/* SCOPES */ /* SCOPES */
/**
* Only query active categories
* @deprecated use ScopeServiceActive
*/
public function scopeActive($query)
{
throw new \Exception('deprecated');
return $query->where(
fn($query)=>
$query->where($this->getTable().'.active',TRUE)
->orWhereNotIn('order_status',self::INACTIVE_STATUS)
);
}
/**
* Find inactive services.
*
* @param $query
* @return mixed
* @deprecated use ScopeServiceInactive
*/
public function scopeInactive($query)
{
dd('deprecated');
return $query->where(
fn($query)=>
$query->where($this->getTable().'.active',FALSE)
->orWhereIn('order_status',self::INACTIVE_STATUS)
);
}
/** /**
* Search for a record * Search for a record
* *
@ -547,6 +525,62 @@ class Service extends Model implements IDs
/* ATTRIBUTES */ /* 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),
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 * Return the earliest date that the service can be canceled as per contract/billing intervals
* *
@ -584,7 +618,7 @@ class Service extends Model implements IDs
if (! $this->start_at) if (! $this->start_at)
return $this->type->expire_at; return $this->type->expire_at;
$end = $this->start_at->clone()->addMonths($this->contract_term); $end = $this->start_at->clone()->addMonths($this->getContractTermAttribute());
// If we dont have an expire date, use the start date + contract_term // If we dont have an expire date, use the start date + contract_term
if (! $this->type->expire_at) if (! $this->type->expire_at)
@ -594,6 +628,17 @@ class Service extends Model implements IDs
return ($end < $this->type->expire_at) ? $this->type->expire_at : $end; return ($end < $this->type->expire_at) ? $this->type->expire_at : $end;
} }
/**
* This function will determine the minimum contract term for a service, which is the maximum of
* supplied->contract_term, or the product->type->contract_term;
*
* @return int
*/
public function getContractTermAttribute(): int
{
return max($this->supplied->contract_term,$this->product->type->contract_term);
}
/** /**
* Return the date for the next invoice * Return the date for the next invoice
* *
@ -647,7 +692,9 @@ class Service extends Model implements IDs
*/ */
public function getNameShortAttribute() public function getNameShortAttribute()
{ {
return $this->type->getServiceNameAttribute() ?: 'SID:'.$this->sid; return $this->type->getServiceNameAttribute()
? $this->type->getServiceNameAttribute()
: 'SID:'.$this->sid;
} }
/** /**
@ -708,6 +755,18 @@ class Service extends Model implements IDs
: NULL; : NULL;
} }
/**
* Return the Service Status
*
* @return string
*/
public function getStatusAttribute(): string
{
return $this->active
? strtolower($this->order_status)
: ((strtolower($this->order_status) === 'cancelled') ? 'cancelled' : 'inactive');
}
/** /**
* Return the product that supplies this service * Return the product that supplies this service
* ie: product/* * ie: product/*
@ -858,14 +917,14 @@ class Service extends Model implements IDs
* *
* @return float * @return float
*/ */
private function billing_charge(): float public function billing_charge(): float
{ {
// If recur_schedule is null, and we have already invoiced, then nothing further required // If recur_schedule is null, then we only bill this item once
if (is_null($this->billing_interval) && $this->getInvoicedToAttribute()) if (is_null($this->getBillingIntervalAttribute()) && $this->getInvoicedToAttribute())
$this->price = 0; $this->price = 0;
return is_null($this->price) return is_null($this->price)
? $this->billing_charge_orig ? $this->product->getBaseChargeAttribute($this->getBillingIntervalAttribute(),$this->account->group)
: $this->price; : $this->price;
} }
@ -886,12 +945,12 @@ class Service extends Model implements IDs
$max = max($date,$this->getCancelDateAttribute())->clone(); $max = max($date,$this->getCancelDateAttribute())->clone();
if (! $this->getPaidToAttribute()) if (! $this->getPaidToAttribute())
return $this->contract_term*$this->billing_charge_normalised_taxed; return $this->account->taxed($this->getContractTermAttribute()*$this->getBillingChargeNormalisedAttribute());
if ($this->getPaidToAttribute()->lessThan($max)) { if ($this->getPaidToAttribute()->lessThan($max)) {
$d = $this->getPaidToAttribute()->diffInDays($max); $d = $this->getPaidToAttribute()->diffInDays($max);
return $d/30*$this->billing_charge_normalised_taxed; return $this->account->taxed($d/30*$this->getBillingChargeNormalisedAttribute());
} }
return 0; return 0;
@ -902,11 +961,11 @@ class Service extends Model implements IDs
* *
* @return float * @return float
*/ */
private function billing_cost(): float public function billing_cost(): float
{ {
return is_null($this->cost) return is_null($this->cost)
? $this->product->getBaseCostAttribute() ? $this->product->getBaseCostAttribute()
: $this->cost*Invoice::billing_change($this->product->type->billing_interval,$this->billing_interval); : $this->cost*Invoice::billing_change($this->product->type->billing_interval,$this->product->billing_interval);
} }
/** /**
@ -926,12 +985,12 @@ class Service extends Model implements IDs
$max = max($date,$this->getCancelDateAttribute())->clone(); $max = max($date,$this->getCancelDateAttribute())->clone();
if (! $this->getInvoicedToAttribute()) if (! $this->getInvoicedToAttribute())
return $this->contract_term*$this->billing_cost_normalised_taxed; return $this->account->taxed($this->getContractTermAttribute()*$this->getBillingChargeNormalisedAttribute());
if ($this->getInvoicedToAttribute()->lessThan($max)) { if ($this->getInvoicedToAttribute()->lessThan($max)) {
$d = $this->getInvoicedToAttribute()->diffInDays($max); $d = $this->getInvoicedToAttribute()->diffInDays($max);
return $d/30*$this->billing_cost_normalised_taxed; return $this->account->taxed($d/30*$this->getBillingCostNormalisedAttribute());
} }
return 0; return 0;
@ -988,6 +1047,17 @@ class Service extends Model implements IDs
->get(); ->get();
} }
/**
* Determine if a service is active. It is active, if active=1, or the order_status is not in self::INACTIVE_STATUS[]
*
* @return bool
*/
public function isActive(): bool
{
return $this->attributes['active']
|| ($this->order_status && (! in_array($this->order_status,self::INACTIVE_STATUS)));
}
/** /**
* Determine if the current user has the role for this service * Determine if the current user has the role for this service
* *
@ -1037,6 +1107,53 @@ class Service extends Model implements IDs
return FALSE; return FALSE;
} }
/**
* Do we bill for this service
*
* @return bool
*/
public function isBilled(): bool
{
return ! ($this->external_billing || $this->suspend_billing || ($this->price === 0));
}
/**
* Has the price for this service been overridden
*
* @return bool
*/
public function isChargeOverridden(): bool
{
return ! is_null($this->price);
}
public function isCostOverridden(): bool
{
return ! is_null($this->cost);
}
public function isContract(): bool
{
return $this->getContractEndAttribute() && $this->getContractEndAttribute()->greaterThan(Carbon::now());
}
/**
* Identify if a service is being ordered, ie: not active yet nor cancelled
*
* @return bool
*/
public function isPending(): bool
{
return (! $this->active)
&& (! is_null($this->order_status))
&& (! in_array($this->order_status,array_merge(self::INACTIVE_STATUS,['INACTIVE'])));
}
public function isPendingCancel(): bool
{
return in_array(strtolower($this->order_status),['cancel-request','cancel-pending']);
}
/** /**
* Generate a collection of invoice_item objects that will be billed for the next invoice * Generate a collection of invoice_item objects that will be billed for the next invoice
* *
@ -1046,7 +1163,7 @@ class Service extends Model implements IDs
*/ */
public function next_invoice_items(Carbon $billdate=NULL): Collection public function next_invoice_items(Carbon $billdate=NULL): Collection
{ {
if ($this->is_cancelled || (! $this->is_billed)) if ($this->wasCancelled() || (! $this->isBilled()))
return collect(); return collect();
$o = collect(); $o = collect();
@ -1055,7 +1172,7 @@ class Service extends Model implements IDs
// Connection charges are only charged once, so ignore if if we have already billed them // 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()) if ((! $this->invoiced_items()->where('item_type',InvoiceItem::INVOICEITEM_SETUP)->count())
&& (InvoiceItem::distinct('invoice_id')->where('service_id',$this->id)->count() < 2) && (InvoiceItem::distinct('invoice_id')->where('service_id',$this->id)->count() < 2)
&& $this->product->getSetupChargeAttribute($this->billing_interval,$this->account->group)) && $this->product->getSetupChargeAttribute($this->getBillingIntervalAttribute(),$this->account->group))
{ {
$ii = new InvoiceItem; $ii = new InvoiceItem;
@ -1063,7 +1180,7 @@ class Service extends Model implements IDs
$ii->service_id = $this->id; $ii->service_id = $this->id;
$ii->product_id = $this->product_id; $ii->product_id = $this->product_id;
$ii->item_type = InvoiceItem::INVOICEITEM_SETUP; $ii->item_type = InvoiceItem::INVOICEITEM_SETUP;
$ii->price_base = $this->product->getSetupChargeAttribute($this->billing_interval,$this->account->group); $ii->price_base = $this->product->getSetupChargeAttribute($this->getBillingIntervalAttribute(),$this->account->group);
$ii->start_at = $this->invoice_next; $ii->start_at = $this->invoice_next;
$ii->stop_at = $this->invoice_next; $ii->stop_at = $this->invoice_next;
$ii->quantity = 1; $ii->quantity = 1;
@ -1079,14 +1196,14 @@ class Service extends Model implements IDs
while ($invoiced_to <= ($this->stop_at ?: $billdate)) { while ($invoiced_to <= ($this->stop_at ?: $billdate)) {
$ii = new InvoiceItem; $ii = new InvoiceItem;
$period = Invoice::invoice_period($invoiced_to,$this->billing_interval,(bool)$this->product->price_recur_strict); $period = Invoice::invoice_period($invoiced_to,$this->getBillingIntervalAttribute(),(bool)$this->product->price_recur_strict);
$ii->active = TRUE; $ii->active = TRUE;
$ii->service_id = $this->id; $ii->service_id = $this->id;
$ii->product_id = $this->product_id; $ii->product_id = $this->product_id;
$ii->item_type = InvoiceItem::INVOICEITEM_SERVICE; $ii->item_type = InvoiceItem::INVOICEITEM_SERVICE;
$ii->price_base = $this->billing_charge; $ii->price_base = $this->billing_charge();
$ii->recur_schedule = $this->billing_interval; $ii->recur_schedule = $this->getBillingIntervalAttribute();
$ii->start_at = $invoiced_to; $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->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); $ii->quantity = Invoice::invoice_quantity($ii->start_at,$ii->stop_at,$period);
@ -1135,4 +1252,14 @@ class Service extends Model implements IDs
{ {
return $this->order_info ? $this->order_info->get($key) : NULL; return $this->order_info ? $this->order_info->get($key) : NULL;
} }
/**
* Service that was cancelled or never provisioned
*
* @return bool
*/
public function wasCancelled(): bool
{
return in_array($this->order_status,self::INACTIVE_STATUS);
}
} }

View File

@ -1,7 +0,0 @@
@if($change)
<div class="ribbon-wrapper ribbon-lg">
<div class="ribbon bg-warning">
Change
</div>
</div>
@endif

View File

@ -1,7 +0,0 @@
@if($pending)
<div class="ribbon-wrapper ribbon-lg">
<div class="ribbon bg-warning">
Pending
</div>
</div>
@endif

View File

@ -24,7 +24,7 @@
<td>{{ $so->stop_at ? $so->stop_at->format('Y-m-d') : '-' }}</td> <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->invoice_to ? $so->invoice_to->format('Y-m-d') : '-' }}</td>
<td>{{ $so->active ? 'YES' : 'NO' }}</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">{{ $a=number_format($so->billing_charge_normalised,2) }}</td>
<td class="text-right">{{ $b=number_format($so->product->cost_normalized(),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> <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> </tr>

View File

@ -32,7 +32,7 @@
<div class="col-8 col-sm-5 col-md-12 col-lg-6"> <div class="col-8 col-sm-5 col-md-12 col-lg-6">
<x-leenooks::form.date name="change_date" icon="fa-calendar" label="Request Change Date" :value="Carbon::now()->addDays(7)->format('Y-m-d')"/> <x-leenooks::form.date name="change_date" icon="fa-calendar" label="Request Change Date" :value="Carbon::now()->addDays(7)->format('Y-m-d')"/>
</div> </div>
@if($so->is_contracted) @if($so->isContract())
<div class="col-12 col-sm-7 col-md-12 col-lg-6"> <div class="col-12 col-sm-7 col-md-12 col-lg-6">
<strong>NOTE</strong>: This service is in a contract until <strong>{{ $so->contract_end->format('Y-m-d') }}</strong>, thus it may not be able to change plans. <strong>NOTE</strong>: This service is in a contract until <strong>{{ $so->contract_end->format('Y-m-d') }}</strong>, thus it may not be able to change plans.
</div> </div>

View File

@ -45,9 +45,9 @@
<td>{{ $oo->service_expire ? $oo->service_expire->format('Y-m-d') : '-' }}</td> <td>{{ $oo->service_expire ? $oo->service_expire->format('Y-m-d') : '-' }}</td>
<td>{{ $oo->registrar->name }}</td> <td>{{ $oo->registrar->name }}</td>
<td>{{ $oo->registrar_ns }}</td> <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->isBilled()) {{ $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>@if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items()->sum('total'),2) }}@else - @endif</td>
<td>{{ $oo->service->billing_interval_name }}</td> <td>{{ $oo->service->billing_interval_string }}</td>
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>

View File

@ -49,9 +49,9 @@
<td>{{ $oo->admin_url }}</td> <td>{{ $oo->admin_url }}</td>
<td>@if($oo->admin_user){{ $oo->admin_user }}/{{ $oo->admin_pass }}@else &nbsp; @endif</td> <td>@if($oo->admin_user){{ $oo->admin_user }}/{{ $oo->admin_pass }}@else &nbsp; @endif</td>
<td class="text-right">{{ number_format($oo->accounts ?: 0) }}</td> <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->isBilled()) {{ $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>@if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items()->sum('total'),2) }}@else - @endif</td>
<td>{{ $oo->service->billing_interval_name }}</td> <td>{{ $oo->service->billing_interval_string }}</td>
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>

View File

@ -40,7 +40,7 @@
<li class="nav-item ml-auto"><a class="nav-link" href="#billing" data-toggle="tab">Billing History</a></li> <li class="nav-item ml-auto"><a class="nav-link" href="#billing" data-toggle="tab">Billing History</a></li>
<li class="nav-item"><a class="nav-link" href="#internal" data-toggle="tab">Internal</a></li> <li class="nav-item"><a class="nav-link" href="#internal" data-toggle="tab">Internal</a></li>
<li class="nav-item"><a @class(['nav-link','active'=>session()->has('service_update')]) href="#update" data-toggle="tab">Update</a></li> <li class="nav-item"><a @class(['nav-link','active'=>session()->has('service_update')]) href="#update" data-toggle="tab">Update</a></li>
@if($o->active || $o->is_pending_active) @if($o->active || $o->isPending())
<li class="nav-item"><a @class(['nav-link','active'=>session()->has('charge_add')]) href="#charge" data-toggle="tab">Charge</a></li> <li class="nav-item"><a @class(['nav-link','active'=>session()->has('charge_add')]) href="#charge" data-toggle="tab">Charge</a></li>
@endif @endif
@endcan @endcan
@ -95,7 +95,7 @@
@include('theme.backend.adminlte.service.widget.update') @include('theme.backend.adminlte.service.widget.update')
</div> </div>
@if($o->active || $o->is_pending_active) @if($o->active || $o->isPending())
<div @class(['tab-pane','fade','show active'=>session()->pull('charge_add')]) id="charge"> <div @class(['tab-pane','fade','show active'=>session()->pull('charge_add')]) id="charge">
@include('theme.backend.adminlte.service.widget.charge') @include('theme.backend.adminlte.service.widget.charge')
</div> </div>

View File

@ -43,9 +43,9 @@
<td>{{ $oo->service->name }}</td> <td>{{ $oo->service->name }}</td>
<td>{{ $oo->service_expire ? $oo->service_expire->format('Y-m-d') : '-' }}</td> <td>{{ $oo->service_expire ? $oo->service_expire->format('Y-m-d') : '-' }}</td>
<td>{{ $oo->service->product->supplier->name }}</td> <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->isBilled()) {{ $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>@if (! $oo->service->external_billing)${{ number_format($oo->service->next_invoice_items()->sum('total'),2) }}@else - @endif</td>
<td>{{ $oo->service->billing_interval_name }}</td> <td>{{ $oo->service->billing_interval_string }}</td>
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>

View File

@ -40,8 +40,8 @@
<td>{{ $o->name }}</td> <td>{{ $o->name }}</td>
<td>{{ $o->product->name }}</td> <td>{{ $o->product->name }}</td>
<td>{{ $o->supplierid }}</td> <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_charge_normalised,2) }}</td>
<td class="text-right">{{ number_format($o->billing_cost_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 class="text-right">{{ $o->product->hasUsage() ? number_format($o->type->usage_summary(0)->sum()/1000,1) : '-' }}</td>
<td>{{ $o->product->supplier->name }}</td> <td>{{ $o->product->supplier->name }}</td>
</tr> </tr>

View File

@ -1,8 +1,13 @@
<!-- $o=Service\Broadband::class --> <!-- $o=Service\Broadband::class -->
<div class="card"> <div class="card">
<x-ribbons.change :change="$o->service->is_pending_change"/> @if($o->service->isPending())
<x-ribbons.pending :pending="$o->service->is_pending_active"/> <div class="ribbon-wrapper ribbon-lg">
<div class="ribbon bg-warning">
Pending
</div>
</div>
@endif
<div class="card-header bg-gray-dark"> <div class="card-header bg-gray-dark">
<h3 class="card-title">Broadband Details</h3> <h3 class="card-title">Broadband Details</h3>

View File

@ -1,8 +1,13 @@
<!-- $o=Service\Domain::class --> <!-- $o=Service\Domain::class -->
<div class="card"> <div class="card">
<x-ribbons.change :change="$o->service->is_pending_change"/> @if($o->service->isPending())
<x-ribbons.pending :pending="$o->service->is_pending_active"/> <div class="ribbon-wrapper ribbon-lg">
<div class="ribbon bg-warning">
Pending
</div>
</div>
@endif
<div class="card-header bg-gray-dark"> <div class="card-header bg-gray-dark">
<h3 class="card-title">Domain Details</h3> <h3 class="card-title">Domain Details</h3>

View File

@ -1,8 +1,13 @@
<!-- $o=Service\Email::class --> <!-- $o=Service\Email::class -->
<div class="card"> <div class="card">
<x-ribbons.change :change="$o->service->is_pending_change"/> @if($o->service->isPending())
<x-ribbons.pending :pending="$o->service->is_pending_active"/> <div class="ribbon-wrapper ribbon-lg">
<div class="ribbon bg-warning">
Pending
</div>
</div>
@endif
<div class="card-header bg-gray-dark"> <div class="card-header bg-gray-dark">
<h3 class="card-title">Email Hosting Details</h3> <h3 class="card-title">Email Hosting Details</h3>

View File

@ -1,8 +1,13 @@
<!-- $o=Service\Host::class --> <!-- $o=Service\Host::class -->
<div class="card"> <div class="card">
<x-ribbons.change :change="$o->service->is_pending_change"/> @if($o->service->isPending())
<x-ribbons.pending :pending="$o->service->is_pending_active"/> <div class="ribbon-wrapper ribbon-lg">
<div class="ribbon bg-warning">
Pending
</div>
</div>
@endif
<div class="card-header bg-gray-dark"> <div class="card-header bg-gray-dark">
<h3 class="card-title">Hosting Details</h3> <h3 class="card-title">Hosting Details</h3>

View File

@ -22,7 +22,7 @@
<th>Status</th> <th>Status</th>
<td><x-button.status :status="$o->status" :substatus="$o->order_status"/></td> <td><x-button.status :status="$o->status" :substatus="$o->order_status"/></td>
</tr> </tr>
@if($o->is_pending_cancel) @if($o->isPendingCancel())
<tr> <tr>
<th>Cancel Date</th> <th>Cancel Date</th>
<td>{{ $o->stop_at->format('Y-m-d') }}</td> <td>{{ $o->stop_at->format('Y-m-d') }}</td>
@ -34,26 +34,26 @@
<td>{{ $o->order_info_reference ?? '' }}</td> <td>{{ $o->order_info_reference ?? '' }}</td>
</tr> </tr>
@endif @endif
@if($o->start_at && $o->is_pending_active) @if($o->start_at && $o->isPending())
<tr> <tr>
<th>Pending Connection</th> <th>Pending Connection</th>
<td>{{ $o->start_at->format('Y-m-d') }}</td> <td>{{ $o->start_at->format('Y-m-d') }}</td>
</tr> </tr>
@endif @endif
@if(($o->active || $o->is_pending_active) && (! $o->external_billing)) @if(($o->active || $o->isPending()) && (! $o->external_billing))
<tr> <tr>
<th>Billed</th> <th>Billed</th>
<td>{{ $o->billing_interval_name }}</td> <td>{{ $o->billing_interval_string }}</td>
</tr> </tr>
<tr> <tr>
<th>Amount</th> <th>Amount</th>
@if($o->is_charge_overridden) @if($o->isChargeOverridden())
<td>@if($o->billing_charge < $o->billing_charge_orig)<del>${{ number_format($o->billing_charge_orig_taxed,2) }}</del> @endif${{ number_format($o->billing_charge_taxed,2) }}</td> <td>@if($o->billing_charge < $o->charge_orig)<del>${{ number_format($o->charge_orig,2) }}</del> @endif${{ number_format($o->billing_charge,2) }}</td>
@else @else
<td>${{ number_format($o->billing_charge_taxed,2) }}</td> <td>${{ number_format($o->billing_charge,2) }}</td>
@endif @endif
</tr> </tr>
@if($o->is_active && $o->invoiced_to) @if($o->isActive() && $o->invoiced_to)
<tr> <tr>
<th>Invoiced To</th> <th>Invoiced To</th>
<td>{{ $o->invoiced_to->format('Y-m-d') }}</td> <td>{{ $o->invoiced_to->format('Y-m-d') }}</td>
@ -83,7 +83,7 @@
<td>@if($o->billing)Direct Debit @else Invoice @endif</td> <td>@if($o->billing)Direct Debit @else Invoice @endif</td>
</tr> </tr>
@elseif($o->is_cancelled) @elseif($o->wasCancelled())
<tr> <tr>
<th>Cancelled</th> <th>Cancelled</th>
<td>{{ ($o->stop_at ?: $o->paid_to)?->format('Y-m-d') }}</td> <td>{{ ($o->stop_at ?: $o->paid_to)?->format('Y-m-d') }}</td>
@ -92,7 +92,7 @@
</table> </table>
</div> </div>
@if($o->active || $o->is_pending_active) @if($o->active || $o->isPending())
<div class="card-footer sm"> <div class="card-footer sm">
<strong><sup>*</sup>NOTE:</strong> Estimated Invoice does not include any setup, connection nor all current billing cycle usage charges. <strong><sup>*</sup>NOTE:</strong> Estimated Invoice does not include any setup, connection nor all current billing cycle usage charges.
</div> </div>

View File

@ -16,7 +16,6 @@
<th>&nbsp;</th> <th>&nbsp;</th>
@endif @endif
</tr> </tr>
<tr> <tr>
<th>&nbsp;</th> <th>&nbsp;</th>
<th>Client</th> <th>Client</th>
@ -33,14 +32,13 @@
<tbody> <tbody>
<tr> <tr>
<th>Product</th> <th>Product</th>
<td><a href="{{ route('product',['pdo'=>$o->product_id]) }}">#{{ $o->product_id }}: {{ $o->product->name }}</a></td> <td><a href="{{ url('a/product/details',$o->product_id) }}">#{{ $o->product_id }}: {{ $o->product->name }}</a></td>
<td><a href="{{ route('supplier.product.type',['id'=>$c->supplied->id,'spo'=>$c->supplied->supplier_detail_id,'type'=>$c->supplied->category]) }}">#{{ $c->supplied->id }}: {{ $c->supplied->name_long }}</a></td> <td><a href="{{ url('a/product/details',$c->id) }}">#{{ $c->supplied->id }}: {{ $c->supplied->name_long }}</a></td>
<td>{{ $c->category_name }}</td>
@if($p->exists) @if($p->exists)
<th><a href="{{ route('product',['pdo'=>$p->id]) }}">#{{ $p->id }}: {{ $p->name }}</a></th> <th>&nbsp;</th>
<td class="text-center"><a href="{{ route('supplier.product.type',['id'=>$p->supplied->id,'spo'=>$p->supplied->supplier_detail_id,'type'=>$p->supplied->category]) }}">#{{ $p->supplied->id }}: {{ $p->supplied->name_long }}</a></td> <td class="text-center" colspan="2">#{{ $p->supplied->id }}: {{ $p->supplied->name_long }}</td>
<td>&nbsp;</td>
@endif @endif
<td>{{ $c->category_name }}</td>
</tr> </tr>
<tr> <tr>
@ -57,11 +55,11 @@
<tr> <tr>
<th>Billed</th> <th>Billed</th>
<td>{{ $o->billing_interval_name }}</td> <td>{{ $o->billing_interval_string }}</td>
<td>{{ $c->type->billing_interval_string }}</td> <td>{{ $c->type->billing_interval_string }}</td>
<td>&nbsp;</td> <td>&nbsp;</td>
@if($p->exists) @if($p->exists)
<td>{{ $o->billing_interval_name }}</td> <td>{{ $o->billing_interval_string }}</td>
<td>{{ $p->type->billing_interval_string }}</td> <td>{{ $p->type->billing_interval_string }}</td>
<td>&nbsp;</td> <td>&nbsp;</td>
@endif @endif
@ -69,11 +67,11 @@
<tr> <tr>
<th>Billing Price</th> <th>Billing Price</th>
<td @class(['text-danger'=>$o->is_charge_overridden])>${{ number_format($b=$o->billing_charge_taxed,2) }}</td> <td @if($o->isChargeOverridden())class="text-danger"@endif>${{ number_format($b=$o->billing_charge,2) }}</td>
<td @class(['text-danger'=>$o->is_cost_overridden])>${{ number_format($a=$o->billing_cost_taxed,2) }}</td> <td @if($o->isCostOverridden())class="text-danger"@endif>${{ number_format($a=$o->billing_cost,2) }}</td>
<td>{!! markup($a,$b) !!}</td> <td>{!! markup($a,$b) !!}</td>
@if($p->exists) @if($p->exists)
<td @if($o->is_charge_overridden)class="text-danger"@endif>${{ number_format($b=$o->account->taxed($p->base_charge),2) }}</td> <td @if($o->isChargeOverridden())class="text-danger"@endif>${{ number_format($b=$o->account->taxed($p->base_charge),2) }}</td>
<td>${{ number_format($a=$o->account->taxed($p->base_cost),2) }}</td> <td>${{ number_format($a=$o->account->taxed($p->base_cost),2) }}</td>
<td>{!! markup($a,$b) !!}</td> <td>{!! markup($a,$b) !!}</td>
@endif @endif
@ -81,24 +79,24 @@
<tr> <tr>
<th>Monthly Price</th> <th>Monthly Price</th>
<td @class(['text-danger'=>$o->is_charge_overridden])> <td @if($x=$o->isChargeOverridden()) class="text-danger" @endif>
@if($o->is_charge_overridden) @if($x)
<abbr title="${{ number_format($o->billing_charge_orig_normalised_taxed,2) }}">${{ number_format($b=$o->billing_charge_normalised_taxed,2) }} <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) }}
@else @else
${{ number_format($b=$o->billing_charge_normalised_taxed,2) }} ${{ number_format($b=$o->billing_charge_normalised,2) }}
@endif @endif
</td> </td>
<td @class(['text-danger'=>$o->is_cost_overridden])> <td @if($x=$o->isCostOverridden()) class="text-danger" @endif>
@if($o->is_cost_overridden) @if($x)
<abbr title="${{ number_format($o->billing_cost_orig_normalised_taxed,2) }}">${{ number_format($a=$o->billing_cost_normalised_taxed,2) }} <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) }}
@else @else
${{ number_format($a=$o->billing_cost_normalised_taxed,2) }} ${{ number_format($a=$o->billing_cost_normalised,2) }}
@endif @endif
</td> </td>
<td>{!! markup($a,$b) !!}</td> <td>{!! markup($a,$b) !!}</td>
@if($p->exists) @if($p->exists)
<td @class(['text-danger'=>$o->is_charge_overridden])>${{ number_format($b=$o->account->taxed($p->base_charge)*Invoice::billing_change($o->billing_interval,Invoice::BILL_MONTHLY),2) }}</td> <td @if($x=$o->isChargeOverridden()) class="text-danger" @endif>${{ number_format($b=$o->account->taxed($p->base_charge)*Invoice::billing_change($o->billing_interval,Invoice::BILL_MONTHLY),2) }}</td>
<td @class(['text-danger'=>$o->is_cost_overridden])>${{ number_format($a=$o->account->taxed($p->base_cost)*Invoice::billing_change($o->billing_interval,Invoice::BILL_MONTHLY),2) }}</td> <td>${{ number_format($a=$o->account->taxed($p->base_cost)*Invoice::billing_change($o->billing_interval,Invoice::BILL_MONTHLY),2) }}</td>
<td>{!! markup($a,$b) !!}</td> <td>{!! markup($a,$b) !!}</td>
@endif @endif
</tr> </tr>
@ -131,18 +129,15 @@
<tr> <tr>
<th>Contract Left</th> <th>Contract Left</th>
@if($o->is_contracted) @if($o->isContract())
<td>${{ number_format($o->billing_charge_to($o->contract_end),2) }} (<small>{{ $o->paid_to?->format('Y-m-d') }}</small>)</td> <td>${{ number_format($o->billing_charge_to($o->contract_end),2) }} (<small>{{ $o->paid_to?->format('Y-m-d') }}</small>)</td>
<td>${{ number_format($o->billing_cost_to($o->contract_end),2) }} (<small>{{ $o->invoiced_to?->format('Y-m-d') }}</small>)</td> <td>${{ number_format($o->billing_cost_to($o->contract_end),2) }} (<small>{{ $o->invoiced_to?->format('Y-m-d') }}</small>)</td>
<td>{{ $o->contract_end->format('Y-m-d') }}<br><small>({{ $o->contract_end->diffForHumans(now(),CarbonInterface::DIFF_RELATIVE_TO_OTHER,FALSE,2) }} today)</small></td> <td>{{ $o->contract_end->format('Y-m-d') }}<br><small>({{ $o->contract_end->diffForHumans(now(),CarbonInterface::DIFF_RELATIVE_TO_OTHER,FALSE,2) }} today)</small></td>
@else @else
<td colspan="2" class="text-center">Not on contract</td> <td colspan="2" class="text-center">Not on contract</td>
<td>&nbsp;</td> <td>&nbsp;</td>
@endif @endif
@if($p->exists)
<td colspan="3">&nbsp;</td>
@endif
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -1,8 +1,13 @@
<!-- $o=Service\Phone::class --> <!-- $o=Service\Phone::class -->
<div class="card"> <div class="card">
<x-ribbons.change :change="$o->service->is_pending_change"/> @if($o->service->isPending())
<x-ribbons.pending :pending="$o->service->is_pending_active"/> <div class="ribbon-wrapper ribbon-lg">
<div class="ribbon bg-warning">
Pending
</div>
</div>
@endif
<div class="card-header bg-gray-dark"> <div class="card-header bg-gray-dark">
<h3 class="card-title">Phone Details</h3> <h3 class="card-title">Phone Details</h3>

View File

@ -1,6 +1,11 @@
<div class="card"> <div class="card">
<x-ribbons.change :change="$o->service->is_pending_change"/> @if($o->service->isPending())
<x-ribbons.pending :pending="$o->service->is_pending_active"/> <div class="ribbon-wrapper ribbon-lg">
<div class="ribbon bg-warning">
Pending
</div>
</div>
@endif
<div class="card-header bg-gray-dark"> <div class="card-header bg-gray-dark">
<h3 class="card-title">SSL Details</h3> <h3 class="card-title">SSL Details</h3>

View File

@ -49,8 +49,7 @@
<!-- PRODUCT --> <!-- PRODUCT -->
<x-leenooks::form.select name="product_id" icon="fa-list" label="Product" :helper="$o->product->category_name" <x-leenooks::form.select name="product_id" icon="fa-list" label="Product" :helper="$o->product->category_name"
:value="$o->product_id" :value="$o->product_id"
:options="Product::with(['type.supplied','translate']) :options="Product::get()
->get()
->filter(fn($item)=>($item->category === $o->product->category)) ->filter(fn($item)=>($item->category === $o->product->category))
->sortBy('name') ->sortBy('name')
->map(fn($item)=>[ ->map(fn($item)=>[

View File

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