Rework service, removed redundant code, service invoicing improvements
This commit is contained in:
parent
5f10175b35
commit
0b5bc9e012
@ -3,9 +3,8 @@
|
|||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Config;
|
|
||||||
|
|
||||||
use App\Models\{Service,Site};
|
use App\Models\Service;
|
||||||
|
|
||||||
class ServiceList extends Command
|
class ServiceList extends Command
|
||||||
{
|
{
|
||||||
@ -33,7 +32,7 @@ class ServiceList extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$header = '|%13s|%-14s|%-35s|%-40s|%8s|%17s|%12s|%12s|%12s|%12s|%14s|';
|
$header = '|%5s|%-9s|%-30s|%-30s|%7s|%7s|%10s|%10s|%10s|%10s|%10s|';
|
||||||
|
|
||||||
$this->warn(sprintf($header,
|
$this->warn(sprintf($header,
|
||||||
'ID',
|
'ID',
|
||||||
@ -42,41 +41,42 @@ class ServiceList extends Command
|
|||||||
'Name',
|
'Name',
|
||||||
'Active',
|
'Active',
|
||||||
'Status',
|
'Status',
|
||||||
'Next Invoice',
|
'Start',
|
||||||
'Start Date',
|
'Stop',
|
||||||
'Stop Date',
|
'Connect',
|
||||||
'Connect Date',
|
'First',
|
||||||
'First Invoice'
|
'Next',
|
||||||
));
|
));
|
||||||
|
|
||||||
foreach (Service::withoutGlobalScope(\App\Models\Scopes\SiteScope::class)->with(['site'])->cursor() as $o) {
|
foreach (Service::cursor() as $o) {
|
||||||
if ((! $this->option('inactive')) AND ! $o->isActive())
|
if ((! $this->option('inactive')) && (! $o->isActive()))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Config::set('site',$o->site);
|
if ($this->option('type') && ($o->product->getCategoryAttribute() !== $this->option('type')))
|
||||||
|
|
||||||
if ($this->option('type') AND ($o->product->getCategoryAttribute() !== $this->option('type')))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
$c = $o->invoice_items->filter(function($item) {return $item->item_type === 0; })->sortby('start_at')->first();
|
$c = $o->invoiced_items
|
||||||
|
->filter(fn($item)=>$item->item_type === 0)
|
||||||
|
->sortby('start_at')
|
||||||
|
->first();
|
||||||
|
|
||||||
if ($this->option('fix') AND ! $o->start_at AND $c AND $c->start_at AND $o->type AND $o->type->connect_at AND $c->start_at->format('Y-m-d') == $o->type->connect_at->format('Y-m-d')) {
|
if ($this->option('fix') && (! $o->start_at) && $c && $c->start_at && $o->type && $o->type->connect_at && $c->start_at->format('Y-m-d') == $o->type->connect_at->format('Y-m-d')) {
|
||||||
$o->start_at = $o->type->connect_at;
|
$o->start_at = $o->type->connect_at;
|
||||||
$o->save();
|
$o->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->info(sprintf($header,
|
$this->info(sprintf($header,
|
||||||
$o->sid,
|
$o->lid,
|
||||||
$o->product->getCategoryNameAttribute(),
|
$o->product->getCategoryNameAttribute(),
|
||||||
substr($o->product->getNameAttribute(),0,35),
|
substr($o->product->getNameAttribute(),0,35),
|
||||||
substr($o->name_short,0,40),
|
substr($o->name_short,0,40),
|
||||||
$o->active ? 'active' : 'inactive',
|
$o->active ? 'active' : 'inactive',
|
||||||
$o->status,
|
$o->status,
|
||||||
$o->invoice_next?->format('Y-m-d'),
|
|
||||||
$o->start_at?->format('Y-m-d'),
|
$o->start_at?->format('Y-m-d'),
|
||||||
$o->stop_at?->format('Y-m-d'),
|
$o->stop_at?->format('Y-m-d'),
|
||||||
($o->type AND $o->type->connect_at) ? $o->type->connect_at->format('Y-m-d') : NULL,
|
($o->type && $o->type->connect_at) ? $o->type->connect_at->format('Y-m-d') : NULL,
|
||||||
($c && $c->date_start) ? $c->date_start->format('Y-m-d') : NULL,
|
($c && $c->start_at) ? $c->start_at->format('Y-m-d') : NULL,
|
||||||
|
$o->invoice_next?->format('Y-m-d'),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,9 +68,11 @@ class SearchController extends Controller
|
|||||||
foreach (Service::Search($request->input('term'))
|
foreach (Service::Search($request->input('term'))
|
||||||
->whereIN('account_id',$account_ids)
|
->whereIN('account_id',$account_ids)
|
||||||
->orderBy('id')
|
->orderBy('id')
|
||||||
->limit(20)->get() as $o)
|
->limit(20)
|
||||||
|
->with(['product'])
|
||||||
|
->get() as $o)
|
||||||
{
|
{
|
||||||
$result->push(['name'=>sprintf('%s (%s) %s',$o->name,$o->lid,$o->active ? '' : '<small>INACT</small>'),'value'=>'/u/service/'.$o->id,'category'=>$o->category_name]);
|
$result->push(['name'=>sprintf('%s (%s) %s',$o->name,$o->lid,$o->active ? '' : '<small>INACT</small>'),'value'=>'/u/service/'.$o->id,'category'=>$o->product->category_name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for an Invoice
|
// Look for an Invoice
|
||||||
@ -93,7 +95,7 @@ class SearchController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
return $result
|
return $result
|
||||||
->sortBy(function($item) { return $item['category'].$item['name']; })
|
->sortBy(fn($item)=>$item['category'].$item['name'])
|
||||||
->values();
|
->values();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -333,7 +333,7 @@ class ServiceController extends Controller
|
|||||||
$start_at = Carbon::create(Arr::get($request->broadband,'start_at'));
|
$start_at = Carbon::create(Arr::get($request->broadband,'start_at'));
|
||||||
|
|
||||||
// Get the invoiced items covering the start_at date
|
// Get the invoiced items covering the start_at date
|
||||||
foreach ($o->invoice_items->filter(function($item) use ($start_at) {
|
foreach ($o->invoiced_items->filter(function($item) use ($start_at) {
|
||||||
return ($item->start_at < $start_at) && ($item->stop_at > $start_at) && ($item->item_type === 0);
|
return ($item->start_at < $start_at) && ($item->stop_at > $start_at) && ($item->item_type === 0);
|
||||||
}) as $iio)
|
}) as $iio)
|
||||||
{
|
{
|
||||||
@ -351,7 +351,7 @@ class ServiceController extends Controller
|
|||||||
$co->stop_at = $iio->stop_at;
|
$co->stop_at = $iio->stop_at;
|
||||||
$co->amount = $iio->price_base;
|
$co->amount = $iio->price_base;
|
||||||
$co->taxable = TRUE; // @todo this should be determined
|
$co->taxable = TRUE; // @todo this should be determined
|
||||||
$co->quantity = -1*$start_at->diff($iio->stop_at)->days/$iio->start_at->diff($iio->stop_at)->days;
|
$co->quantity = -1*$start_at->diffInDays($iio->stop_at)/$iio->start_at->diffInDays($iio->stop_at);
|
||||||
$charges->push($co);
|
$charges->push($co);
|
||||||
|
|
||||||
// Add the new charge
|
// Add the new charge
|
||||||
@ -368,7 +368,7 @@ class ServiceController extends Controller
|
|||||||
$co->stop_at = $iio->stop_at;
|
$co->stop_at = $iio->stop_at;
|
||||||
$co->amount = Arr::get($request->broadband,'price') ?: $po->base_charge;
|
$co->amount = Arr::get($request->broadband,'price') ?: $po->base_charge;
|
||||||
$co->taxable = TRUE; // @todo this should be determined
|
$co->taxable = TRUE; // @todo this should be determined
|
||||||
$co->quantity = $start_at->diff($iio->stop_at)->days/$iio->start_at->diff($iio->stop_at)->days;
|
$co->quantity = $start_at->diffInDays($iio->stop_at)/$iio->start_at->diffInDays($iio->stop_at);
|
||||||
$charges->push($co);
|
$charges->push($co);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -424,10 +424,10 @@ class ServiceController extends Controller
|
|||||||
$request->post(),
|
$request->post(),
|
||||||
$x=collect($o->type->validation())
|
$x=collect($o->type->validation())
|
||||||
->keys()
|
->keys()
|
||||||
->transform(fn($item)=>sprintf('%s.%s',$o->category,$item))
|
->transform(fn($item)=>sprintf('%s.%s',$o->product->category,$item))
|
||||||
->combine(array_values($o->type->validation()))
|
->combine(array_values($o->type->validation()))
|
||||||
->transform(fn($item)=>is_string($item)
|
->transform(fn($item)=>is_string($item)
|
||||||
? preg_replace('/^exclude_without:/',sprintf('exclude_without:%s.',$o->category),$item)
|
? preg_replace('/^exclude_without:/',sprintf('exclude_without:%s.',$o->product->category),$item)
|
||||||
: $item)
|
: $item)
|
||||||
->merge(
|
->merge(
|
||||||
[
|
[
|
||||||
@ -436,7 +436,7 @@ class ServiceController extends Controller
|
|||||||
'recur_schedule' => ['required',Rule::in(collect(Invoice::billing_periods)->keys())],
|
'recur_schedule' => ['required',Rule::in(collect(Invoice::billing_periods)->keys())],
|
||||||
'invoice_next_at' => 'nullable|date',
|
'invoice_next_at' => 'nullable|date',
|
||||||
'price' => 'nullable|numeric',
|
'price' => 'nullable|numeric',
|
||||||
$o->category => 'array|min:1',
|
$o->product->category => 'array|min:1',
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
->toArray()
|
->toArray()
|
||||||
@ -452,13 +452,13 @@ class ServiceController extends Controller
|
|||||||
$validated = collect($validator->validated());
|
$validated = collect($validator->validated());
|
||||||
|
|
||||||
// Store our service type values
|
// Store our service type values
|
||||||
$o->type->forceFill($validated->get($o->category));
|
$o->type->forceFill($validated->get($o->product->category));
|
||||||
|
|
||||||
// Some special handling
|
// Some special handling
|
||||||
switch ($o->category) {
|
switch ($o->product->category) {
|
||||||
case 'broadband':
|
case 'broadband':
|
||||||
// If pppoe is not set, then we dont need username/password
|
// If pppoe is not set, then we dont need username/password
|
||||||
$o->type->pppoe = ($x=data_get($validated,$o->category.'.pppoe',FALSE));
|
$o->type->pppoe = ($x=data_get($validated,$o->product->category.'.pppoe',FALSE));
|
||||||
|
|
||||||
if (! $x) {
|
if (! $x) {
|
||||||
$o->type->service_username = NULL;
|
$o->type->service_username = NULL;
|
||||||
@ -487,7 +487,7 @@ class ServiceController extends Controller
|
|||||||
|
|
||||||
else {
|
else {
|
||||||
// For broadband, start_at is connect_at in the type record
|
// For broadband, start_at is connect_at in the type record
|
||||||
switch ($o->category) {
|
switch ($o->product->category) {
|
||||||
case 'broadband':
|
case 'broadband':
|
||||||
$o->start_at = $o->type->connect_at;
|
$o->start_at = $o->type->connect_at;
|
||||||
break;
|
break;
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\User;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\{Account,Invoice};
|
|
||||||
|
|
||||||
class AccountController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Show the users next invoice
|
|
||||||
*/
|
|
||||||
public function view_invoice_next(Account $o)
|
|
||||||
{
|
|
||||||
$io = new Invoice;
|
|
||||||
$io->account = $o;
|
|
||||||
|
|
||||||
// Get the account services
|
|
||||||
$s = $o->services(TRUE)
|
|
||||||
->with(['invoice_items','charges'])
|
|
||||||
->get()
|
|
||||||
->filter(function($item) {
|
|
||||||
return ! $item->suspend_billing AND ! $item->external_billing;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get our invoice due date for this invoice
|
|
||||||
$io->due_at = $s->min(function($item) { return $item->invoice_next; });
|
|
||||||
|
|
||||||
// @todo The days in advance is an application parameter
|
|
||||||
$io->created_at = $io->due_at->subDays(30);
|
|
||||||
|
|
||||||
// Work out items to add to this invoice, plus any in the next additional days
|
|
||||||
$days = now()->diffInDays($io->due_at)+1+7;
|
|
||||||
foreach ($s as $so)
|
|
||||||
{
|
|
||||||
if ($so->isInvoiceDueSoon($days))
|
|
||||||
foreach ($so->next_invoice_items() as $o)
|
|
||||||
$io->items->push($o);
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('theme.backend.adminlte.u.invoice.home')
|
|
||||||
->with('o',$io);
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,9 +15,4 @@ class ReportController extends Controller
|
|||||||
{
|
{
|
||||||
return view('product/report');
|
return view('product/report');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function services()
|
|
||||||
{
|
|
||||||
return view('service/report');
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -131,7 +131,7 @@ class ImportCosts implements ShouldQueue
|
|||||||
if ($so) {
|
if ($so) {
|
||||||
// r[1] = Monthly Charge or Extra Charge,r[2] = "On Plan", r[3] = Plan Info
|
// r[1] = Monthly Charge or Extra Charge,r[2] = "On Plan", r[3] = Plan Info
|
||||||
$r = [];
|
$r = [];
|
||||||
switch ($so->category) {
|
switch ($so->product->category) {
|
||||||
case 'broadband':
|
case 'broadband':
|
||||||
$to = Cost\Broadband::where('site_id',$this->co->site_id)
|
$to = Cost\Broadband::where('site_id',$this->co->site_id)
|
||||||
->where('cost_id',$this->co->id)
|
->where('cost_id',$this->co->id)
|
||||||
@ -192,8 +192,8 @@ class ImportCosts implements ShouldQueue
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
dump(['so'=>$so,'category'=>$so->category,'line'=>$line,'m'=>$m,'r'=>$r]);
|
dump(['so'=>$so,'category'=>$so->product->category,'line'=>$line,'m'=>$m,'r'=>$r]);
|
||||||
throw new \Exception(sprintf('ERROR: Service type not handled for service [%s] (%s) on line [%d]',$m[1],$so->category,$c));
|
throw new \Exception(sprintf('ERROR: Service type not handled for service [%s] (%s) on line [%d]',$m[1],$so->product->category,$c));
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -14,7 +14,7 @@ class OrderRequestApprove extends Mailable
|
|||||||
{
|
{
|
||||||
use Queueable, SerializesModels;
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
public Service $service;
|
public Service $so;
|
||||||
public string $notes;
|
public string $notes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,7 +25,7 @@ class OrderRequestApprove extends Mailable
|
|||||||
*/
|
*/
|
||||||
public function __construct(Service $o,string $notes='')
|
public function __construct(Service $o,string $notes='')
|
||||||
{
|
{
|
||||||
$this->service = $o;
|
$this->so = $o;
|
||||||
$this->notes = $notes;
|
$this->notes = $notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,14 +36,14 @@ class OrderRequestApprove extends Mailable
|
|||||||
*/
|
*/
|
||||||
public function build()
|
public function build()
|
||||||
{
|
{
|
||||||
Config::set('site',$this->service->site);
|
Config::set('site',$this->so->site);
|
||||||
|
|
||||||
// @todo This is not consistent with Cancel/Change Request
|
// @todo This is not consistent with Cancel/Change Request
|
||||||
switch ($this->service->category) {
|
switch ($this->so->product->category) {
|
||||||
case 'broadband': $subject = sprintf('%s: %s',$this->service->category,$this->service->type->service_address);
|
case 'broadband': $subject = sprintf('%s: %s',$this->so->product->category,$this->so->type->service_address);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'phone': $subject = sprintf('%s: %s',$this->service->category,$this->service->type->service_number);
|
case 'phone': $subject = sprintf('%s: %s',$this->so->product->category,$this->so->type->service_number);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -53,6 +53,6 @@ class OrderRequestApprove extends Mailable
|
|||||||
return $this
|
return $this
|
||||||
->markdown('email.admin.order.approve')
|
->markdown('email.admin.order.approve')
|
||||||
->subject($subject)
|
->subject($subject)
|
||||||
->with(['site'=>$this->service->site]);
|
->with(['site'=>$this->so->site]);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -156,7 +156,7 @@ class Account extends Model implements IDs
|
|||||||
public function services_active()
|
public function services_active()
|
||||||
{
|
{
|
||||||
return $this->services()
|
return $this->services()
|
||||||
->active();
|
->ServiceActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -158,6 +158,148 @@ class Invoice extends Model implements IDs
|
|||||||
return ceil(($contract_term ?: 1)/(Arr::get(self::billing_periods,$source.'.interval') ?: 1));
|
return ceil(($contract_term ?: 1)/(Arr::get(self::billing_periods,$source.'.interval') ?: 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Work out the time period for a particular date and invoice period
|
||||||
|
*
|
||||||
|
* @param \Leenooks\Carbon $date
|
||||||
|
* @param int $interval
|
||||||
|
* @param bool $strict
|
||||||
|
* @return Collection
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public static function invoice_period(Carbon $date,int $interval,bool $strict): Collection
|
||||||
|
{
|
||||||
|
$date_start = $date->clone();
|
||||||
|
$date_end = $date->clone();
|
||||||
|
|
||||||
|
switch ($interval) {
|
||||||
|
case self::BILL_WEEKLY:
|
||||||
|
$result = collect([
|
||||||
|
'start' => $strict
|
||||||
|
? $date_start->startOfWeek()
|
||||||
|
: $date_start,
|
||||||
|
'end'=> $strict
|
||||||
|
? $date_end->endOfWeek()
|
||||||
|
: $date_end->addWeek()->subDay()
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::BILL_MONTHLY:
|
||||||
|
$result = collect([
|
||||||
|
'start' => $strict
|
||||||
|
? $date_start->startOfMonth()
|
||||||
|
: $date_start,
|
||||||
|
'end' => $strict
|
||||||
|
? $date_end->endOfMonth()
|
||||||
|
: $date_end->addMonth()->subDay()
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::BILL_QUARTERLY:
|
||||||
|
$result = collect([
|
||||||
|
'start' => $strict// The service charges
|
||||||
|
? $date_start->startOfQuarter()
|
||||||
|
: $date_start,
|
||||||
|
'end' => $strict
|
||||||
|
? $date_end->endOfQuarter()
|
||||||
|
: $date_end->addQuarter()->subDay()
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::BILL_SEMI_YEARLY:
|
||||||
|
$result = collect([
|
||||||
|
'start' => $strict
|
||||||
|
? $date_start->startOfHalf()
|
||||||
|
: $date_start,
|
||||||
|
'end' => $strict
|
||||||
|
? $date_end->endOfHalf()
|
||||||
|
: $date_end->addQuarters(2)->subDay()
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::BILL_YEARLY:
|
||||||
|
$result = collect([
|
||||||
|
'start' => $strict
|
||||||
|
? $date_start->startOfYear()
|
||||||
|
: $date_start,
|
||||||
|
'end' => $strict
|
||||||
|
? $date_end->endOfYear()
|
||||||
|
: $date_end->addYear()->subDay()
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::BILL_TWOYEARS:
|
||||||
|
if (! $strict) {
|
||||||
|
$result = collect([
|
||||||
|
'start' => $date_start,
|
||||||
|
'end' => $date_end->addYears(2)->subDay(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$data_end = $date_end->addYears(2)->subDay()->endOfYear();
|
||||||
|
|
||||||
|
// Make sure we end on an even year
|
||||||
|
if ($data_end->clone()->addDay()->year%2)
|
||||||
|
$data_end = $data_end->subYear();
|
||||||
|
|
||||||
|
$result = collect([
|
||||||
|
'start' => $data_end->clone()->subYears(2)->addDay(),
|
||||||
|
'end' => $data_end,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// NOTE: price_recur_strict ignored
|
||||||
|
case self::BILL_THREEYEARS:
|
||||||
|
$result = collect([
|
||||||
|
'start' => $date_start,
|
||||||
|
'end' => $date_end->addYears(3)->subDay(),
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// NOTE: price_recur_strict ignored
|
||||||
|
case self::BILL_FOURYEARS:
|
||||||
|
$result = collect([
|
||||||
|
'start' => $date_start,
|
||||||
|
'end' => $date_end->addYears(4)->subDay(),
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// NOTE: price_recur_strict ignored
|
||||||
|
case self::BILL_FIVEYEARS:
|
||||||
|
$result = collect([
|
||||||
|
'start' => $date_start,
|
||||||
|
'end' => $date_end->addYears(5)->subDay(),
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new \Exception('Unknown recur_schedule: '.$interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Leenooks\Carbon $start Start Date
|
||||||
|
* @param Carbon $end End Date
|
||||||
|
* @param int $interval Period End Date
|
||||||
|
* @param bool $strict
|
||||||
|
* @return float
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public static function invoice_quantity(Carbon $start,Carbon $end,Collection $period): float
|
||||||
|
{
|
||||||
|
if ($start->lessThan(Arr::get($period,'start')) || $end->greaterThan(Arr::get($period,'end')))
|
||||||
|
throw new \Exception('Billing Period differ');
|
||||||
|
|
||||||
|
$d = Arr::get($period,'start')->diffInDays(Arr::get($period,'end'));
|
||||||
|
if (! $d)
|
||||||
|
throw new \Exception('Start and End period dates cannot be the same');
|
||||||
|
|
||||||
|
return round(($d-Arr::get($period,'start')->diffInDays($start)-$end->diffInDays(Arr::get($period,'end')))/$d,2);
|
||||||
|
}
|
||||||
|
|
||||||
/* INTERFACES */
|
/* INTERFACES */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,13 +30,16 @@ class InvoiceItem extends Model
|
|||||||
// Array of items that can be updated with PushNew
|
// Array of items that can be updated with PushNew
|
||||||
protected $pushable = ['taxes'];
|
protected $pushable = ['taxes'];
|
||||||
|
|
||||||
|
public const INVOICEITEM_SERVICE = 0;
|
||||||
|
public const INVOICEITEM_SETUP = 4;
|
||||||
|
|
||||||
// @todo Change these to CONSTS so it's easier to reference through out the code
|
// @todo Change these to CONSTS so it's easier to reference through out the code
|
||||||
public const type = [
|
public const type = [
|
||||||
0 => 'Service Charge',
|
self::INVOICEITEM_SERVICE => 'Service Charge',
|
||||||
1 => 'Hardware', // *
|
1 => 'Hardware', // *
|
||||||
2 => 'Service Relocation Fee', // * Must have corresponding SERVICE_ID
|
2 => 'Service Relocation Fee', // * Must have corresponding SERVICE_ID
|
||||||
3 => 'Service Change', // * Must have corresponding SERVICE_ID
|
3 => 'Service Change', // * Must have corresponding SERVICE_ID
|
||||||
4 => 'Service Connection', // * Must have corresponding SERVICE_ID
|
self::INVOICEITEM_SETUP => 'Service Connection', // * Must have corresponding SERVICE_ID
|
||||||
6 => 'Service Cancellation', // * Must have corresponding SERVICE_ID
|
6 => 'Service Cancellation', // * Must have corresponding SERVICE_ID
|
||||||
7 => 'Extra Product/Service Charge', // * Service Billing in advance, Must have corresponding SERVICE_ID
|
7 => 'Extra Product/Service Charge', // * Service Billing in advance, Must have corresponding SERVICE_ID
|
||||||
8 => 'Product Addition', // * Additional Product Customisation, Must have corresponding SERVICE_ID
|
8 => 'Product Addition', // * Additional Product Customisation, Must have corresponding SERVICE_ID
|
||||||
|
@ -16,7 +16,7 @@ use Leenooks\Casts\LeenooksCarbon;
|
|||||||
|
|
||||||
use App\Models\Product\Type;
|
use App\Models\Product\Type;
|
||||||
use App\Interfaces\IDs;
|
use App\Interfaces\IDs;
|
||||||
use App\Traits\ScopeServiceUserAuthorised;
|
use App\Traits\{ScopeServiceActive,ScopeServiceUserAuthorised};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Service
|
* Class Service
|
||||||
@ -29,10 +29,10 @@ use App\Traits\ScopeServiceUserAuthorised;
|
|||||||
*
|
*
|
||||||
* Attributes for services:
|
* Attributes for services:
|
||||||
* + additional_cost : Pending additional charges for this service (excluding setup) //@todo check all these are still valid
|
* + 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 // @todo change to "charge"
|
* + billing_charge : Charge for this service each invoice period
|
||||||
* + billing_interval : The period that this service is billed for by default
|
* + 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
|
* + billing_interval_string : The period that this service is billed for by default as a name
|
||||||
* + billed_to : When this service has been billed to // @todo rename all references to invoice_to
|
* + invoiced_to : When this service has been billed to
|
||||||
* + category : The type of service this is, eg: broadband, phone
|
* + category : The type of service this is, eg: broadband, phone
|
||||||
* + category_name : The type of service this is, eg: Broadband, Telephone (in human friendly)
|
* + category_name : The type of service this is, eg: Broadband, Telephone (in human friendly)
|
||||||
* + contract_term : The term that this service must be active
|
* + contract_term : The term that this service must be active
|
||||||
@ -54,7 +54,7 @@ use App\Traits\ScopeServiceUserAuthorised;
|
|||||||
*/
|
*/
|
||||||
class Service extends Model implements IDs
|
class Service extends Model implements IDs
|
||||||
{
|
{
|
||||||
use HasFactory,ScopeServiceUserAuthorised;
|
use HasFactory,ScopeServiceActive,ScopeServiceUserAuthorised;
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'order_info' => AsCollection::class,
|
'order_info' => AsCollection::class,
|
||||||
@ -281,8 +281,8 @@ class Service extends Model implements IDs
|
|||||||
public static function movements(User $uo): Collection
|
public static function movements(User $uo): Collection
|
||||||
{
|
{
|
||||||
return (new self)
|
return (new self)
|
||||||
->active()
|
->ServiceActive()
|
||||||
->serviceUserAuthorised($uo)
|
->ServiceUserAuthorised($uo)
|
||||||
->where('order_status','!=','ACTIVE')
|
->where('order_status','!=','ACTIVE')
|
||||||
->with(['account','product'])
|
->with(['account','product'])
|
||||||
->get();
|
->get();
|
||||||
@ -343,7 +343,7 @@ class Service extends Model implements IDs
|
|||||||
public function charges_active()
|
public function charges_active()
|
||||||
{
|
{
|
||||||
return $this->charges()
|
return $this->charges()
|
||||||
->active();
|
->ServiceActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -371,6 +371,7 @@ class Service extends Model implements IDs
|
|||||||
*/
|
*/
|
||||||
public function invoice_items($active=TRUE)
|
public function invoice_items($active=TRUE)
|
||||||
{
|
{
|
||||||
|
Log::alert('Call to deprecated functon '.__METHOD__);
|
||||||
return $this->invoiced_items_active();
|
return $this->invoiced_items_active();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -418,6 +419,7 @@ class Service extends Model implements IDs
|
|||||||
{
|
{
|
||||||
return $this->hasMany(InvoiceItem::class)
|
return $this->hasMany(InvoiceItem::class)
|
||||||
->where('item_type','=',0)
|
->where('item_type','=',0)
|
||||||
|
->whereNotNull('start_at')
|
||||||
->orderBy('start_at','desc');
|
->orderBy('start_at','desc');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,9 +466,11 @@ class Service extends Model implements IDs
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Only query active categories
|
* Only query active categories
|
||||||
|
* @deprecated use ScopeServiceActive
|
||||||
*/
|
*/
|
||||||
public function scopeActive($query)
|
public function scopeActive($query)
|
||||||
{
|
{
|
||||||
|
throw new \Exception('deprecated');
|
||||||
return $query->where(
|
return $query->where(
|
||||||
fn($query)=>
|
fn($query)=>
|
||||||
$query->where($this->getTable().'.active',TRUE)
|
$query->where($this->getTable().'.active',TRUE)
|
||||||
@ -479,9 +483,11 @@ class Service extends Model implements IDs
|
|||||||
*
|
*
|
||||||
* @param $query
|
* @param $query
|
||||||
* @return mixed
|
* @return mixed
|
||||||
|
* @deprecated use ScopeServiceInactive
|
||||||
*/
|
*/
|
||||||
public function scopeInActive($query)
|
public function scopeInactive($query)
|
||||||
{
|
{
|
||||||
|
dd('deprecated');
|
||||||
return $query->where(
|
return $query->where(
|
||||||
fn($query)=>
|
fn($query)=>
|
||||||
$query->where($this->getTable().'.active',FALSE)
|
$query->where($this->getTable().'.active',FALSE)
|
||||||
@ -527,15 +533,7 @@ class Service extends Model implements IDs
|
|||||||
*/
|
*/
|
||||||
public function getBillingChargeAttribute(): float
|
public function getBillingChargeAttribute(): float
|
||||||
{
|
{
|
||||||
// If recur_schedule is null, then we only bill this item once
|
return $this->account->taxed($this->billing_charge());
|
||||||
if (is_null($this->recur_schedule) && $this->getInvoiceToAttribute())
|
|
||||||
$this->price = 0;
|
|
||||||
|
|
||||||
return $this->account->taxed(
|
|
||||||
is_null($this->price)
|
|
||||||
? $this->product->getBaseChargeAttribute($this->recur_schedule,$this->account->group)
|
|
||||||
: $this->price
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -546,7 +544,7 @@ class Service extends Model implements IDs
|
|||||||
*/
|
*/
|
||||||
public function getBillingChargeNormalisedAttribute(): float
|
public function getBillingChargeNormalisedAttribute(): float
|
||||||
{
|
{
|
||||||
return number_format($this->getBillingChargeAttribute()*Invoice::billing_change($this->recur_schedule,$this->offering->billing_interval),2);
|
return number_format($this->getBillingChargeAttribute()*Invoice::billing_change($this->getBillingIntervalAttribute(),$this->offering->billing_interval),2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -569,39 +567,6 @@ class Service extends Model implements IDs
|
|||||||
return Invoice::billing_name($this->getBillingIntervalAttribute());
|
return Invoice::billing_name($this->getBillingIntervalAttribute());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine a monthly price for a service, even if it is billed at a different frequency
|
|
||||||
*
|
|
||||||
* @return float
|
|
||||||
* @throws Exception
|
|
||||||
* @deprecated use class::billing_charge_normalised()
|
|
||||||
*/
|
|
||||||
public function getBillingMonthlyPriceAttribute(): float
|
|
||||||
{
|
|
||||||
Log::alert('SMO:! Deprecated function getBillingMonthlyPriceAttribute()');
|
|
||||||
return $this->getBillingChargeNormalisedAttribute();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service Category ID
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getCategoryAttribute(): string
|
|
||||||
{
|
|
||||||
return $this->product->category;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service Category Name
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getCategoryNameAttribute(): string
|
|
||||||
{
|
|
||||||
return $this->product->category_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The date the contract ends
|
* The date the contract ends
|
||||||
*
|
*
|
||||||
@ -652,155 +617,19 @@ class Service extends Model implements IDs
|
|||||||
*/
|
*/
|
||||||
public function getInvoiceNextAttribute(): Carbon
|
public function getInvoiceNextAttribute(): Carbon
|
||||||
{
|
{
|
||||||
$last = $this->getInvoiceToAttribute();
|
$last = $this->getInvoicedToAttribute();
|
||||||
|
|
||||||
return $last
|
return $last
|
||||||
? $last->addDay()
|
? $last->addDay()
|
||||||
: (min($this->start_at,$this->invoice_next_at) ?: Carbon::now());
|
: (min($this->start_at,$this->invoice_next_at) ?: Carbon::now());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the end date for the next invoice
|
|
||||||
*
|
|
||||||
* @return Carbon
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function getInvoiceNextEndAttribute(): Carbon
|
|
||||||
{
|
|
||||||
switch ($this->recur_schedule) {
|
|
||||||
case Invoice::BILL_WEEKLY:
|
|
||||||
$date = $this->product->price_recur_strict
|
|
||||||
? $this->getInvoiceNextAttribute()->endOfWeek()
|
|
||||||
: $this->getInvoiceNextAttribute()->addWeek()->subDay();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Invoice::BILL_MONTHLY:
|
|
||||||
$date = $this->product->price_recur_strict
|
|
||||||
? $this->getInvoiceNextAttribute()->endOfMonth()
|
|
||||||
: $this->getInvoiceNextAttribute()->addMonth()->subDay();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Invoice::BILL_QUARTERLY:
|
|
||||||
$date = $this->product->price_recur_strict
|
|
||||||
? $this->getInvoiceNextAttribute()->endOfQuarter()
|
|
||||||
: $this->getInvoiceNextAttribute()->addQuarter()->subDay();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Invoice::BILL_SEMI_YEARLY:
|
|
||||||
$date = $this->product->price_recur_strict
|
|
||||||
? $this->getInvoiceNextAttribute()->endOfHalf()
|
|
||||||
: $this->getInvoiceNextAttribute()->addQuarters(2)->subDay();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Invoice::BILL_YEARLY:
|
|
||||||
$date = $this->product->price_recur_strict
|
|
||||||
? $this->getInvoiceNextAttribute()->endOfYear()
|
|
||||||
: $this->getInvoiceNextAttribute()->addYear()->subDay();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Invoice::BILL_TWOYEARS:
|
|
||||||
if (! $this->product->price_recur_strict) {
|
|
||||||
$date = $this->getInvoiceNextAttribute()->addYears(2)->subDay();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$date = $this->getInvoiceNextAttribute()->addYears(2)->subDay()->endOfYear();
|
|
||||||
|
|
||||||
// Make sure we end on an even year
|
|
||||||
if ($date->clone()->addDay()->year%2)
|
|
||||||
$date = $date->subYear();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// NOTE: price_recur_strict ignored
|
|
||||||
case Invoice::BILL_THREEYEARS:
|
|
||||||
$date = $this->getInvoiceNextAttribute()->addYears(3)->subDay();
|
|
||||||
break;
|
|
||||||
|
|
||||||
// NOTE: price_recur_strict ignored
|
|
||||||
case Invoice::BILL_FOURYEARS:
|
|
||||||
$date = $this->getInvoiceNextAttribute()->addYears(4)->subDay();
|
|
||||||
break;
|
|
||||||
|
|
||||||
// NOTE: price_recur_strict ignored
|
|
||||||
case Invoice::BILL_FIVEYEARS:
|
|
||||||
$date = $this->getInvoiceNextAttribute()->addYears(5)->subDay();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Exception('Unknown recur_schedule');
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the invoice has an end date, our invoice period shouldnt be greater than that (might be terminating).
|
|
||||||
if ($this->stop_at && ($this->stop_at < $date))
|
|
||||||
$date = $this->stop_at;
|
|
||||||
|
|
||||||
return $date;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine how much quantity (at the charge rate) is required for the next invoice
|
|
||||||
*
|
|
||||||
* @return float
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function getInvoiceNextQuantityAttribute(): float
|
|
||||||
{
|
|
||||||
// If we are not rounding to the first day of the cycle, then it is always a full cycle
|
|
||||||
if (! $this->product->price_recur_strict)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
$n = $this->invoice_next->diffInDays($this->invoice_next_end);
|
|
||||||
|
|
||||||
switch ($this->recur_schedule) {
|
|
||||||
case Invoice::BILL_WEEKLY:
|
|
||||||
$d = $this->invoice_next_end->addWeek()->startOfWeek()->diffInDays($this->invoice_next->startOfWeek());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Invoice::BILL_MONTHLY:
|
|
||||||
$d = $this->invoice_next_end->addMonth()->startOfMonth()->diffInDays($this->invoice_next->startOfMonth());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Invoice::BILL_QUARTERLY:
|
|
||||||
$d = $this->invoice_next_end->startOfQuarter()->diffInDays($this->invoice_next->addQuarter()->startOfQuarter());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Invoice::BILL_SEMI_YEARLY:
|
|
||||||
$d = $this->invoice_next_end->addQuarter(2)->startOfHalf()->diffInDays($this->invoice_next->startOfHalf());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Invoice::BILL_YEARLY:
|
|
||||||
$d = $this->invoice_next_end->addYear()->startOfYear()->diffInDays($this->invoice_next->startOfYear());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Invoice::BILL_TWOYEARS:
|
|
||||||
$d = $this->invoice_next_end->addYear(2)->startOfYear()->diffInDays($this->invoice_next->subyear(2))-1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Invoice::BILL_THREEYEARS:
|
|
||||||
$d = $this->invoice_next_end->addYear(3)->startOfYear()->diffInDays($this->invoice_next->subyear(3))-1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Invoice::BILL_FOURYEARS:
|
|
||||||
$d = $this->invoice_next_end->addYear(3)->startOfYear()->diffInDays($this->invoice_next->subyear(4))-1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Invoice::BILL_FIVEYEARS:
|
|
||||||
$d = $this->invoice_next_end->addYear(3)->startOfYear()->diffInDays($this->invoice_next->subyear(5))-1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Exception('Unknown recur_schedule');
|
|
||||||
}
|
|
||||||
|
|
||||||
return round($n/$d,2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the date that the service has been invoiced to
|
* Get the date that the service has been invoiced to
|
||||||
*
|
*
|
||||||
* @return Carbon|null
|
* @return Carbon|null
|
||||||
*/
|
*/
|
||||||
public function getInvoiceToAttribute(): ?Carbon
|
public function getInvoicedToAttribute(): ?Carbon
|
||||||
{
|
{
|
||||||
return ($x=$this->invoiced_service_items_active_recent)->count()
|
return ($x=$this->invoiced_service_items_active_recent)->count()
|
||||||
? $x->first()->stop_at
|
? $x->first()->stop_at
|
||||||
@ -826,7 +655,9 @@ class Service extends Model implements IDs
|
|||||||
*/
|
*/
|
||||||
public function getNameShortAttribute()
|
public function getNameShortAttribute()
|
||||||
{
|
{
|
||||||
return $this->type->getServiceNameAttribute() ? $this->type->getServiceNameAttribute() : 'SID:'.$this->sid;
|
return $this->type->getServiceNameAttribute()
|
||||||
|
? $this->type->getServiceNameAttribute()
|
||||||
|
: 'SID:'.$this->sid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -841,7 +672,9 @@ class Service extends Model implements IDs
|
|||||||
*/
|
*/
|
||||||
public function getNameDetailAttribute()
|
public function getNameDetailAttribute()
|
||||||
{
|
{
|
||||||
return ($this->type->getServiceDescriptionAttribute() !== NULL) ? $this->type->getServiceDescriptionAttribute() : 'No Description';
|
return ($this->type->getServiceDescriptionAttribute() !== NULL)
|
||||||
|
? $this->type->getServiceDescriptionAttribute()
|
||||||
|
: 'No Description';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -878,22 +711,13 @@ class Service extends Model implements IDs
|
|||||||
->last();
|
->last();
|
||||||
|
|
||||||
return $lastpaid
|
return $lastpaid
|
||||||
? $this->invoiced_service_items_active->where('invoice_id',$lastpaid->id)->where('type',0)->max('stop_at')
|
? $this->invoiced_service_items_active
|
||||||
|
->where('invoice_id',$lastpaid->id)
|
||||||
|
->where('type',0)
|
||||||
|
->max('stop_at')
|
||||||
: NULL;
|
: NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the billing recurring configuration for this service
|
|
||||||
*
|
|
||||||
* @param $value
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function xgetRecurScheduleAttribute($value): int
|
|
||||||
{
|
|
||||||
// If recur_schedule not set, default to quarterly
|
|
||||||
return $value ?? Invoice::BILL_QUARTERLY;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the Service Status
|
* Return the Service Status
|
||||||
*
|
*
|
||||||
@ -1033,6 +857,7 @@ class Service extends Model implements IDs
|
|||||||
public function actions(): Collection
|
public function actions(): Collection
|
||||||
{
|
{
|
||||||
$next = $this->getStageParameters($this->order_status)->get('next');
|
$next = $this->getStageParameters($this->order_status)->get('next');
|
||||||
|
|
||||||
return $next
|
return $next
|
||||||
? $next->map(function($item,$key) {
|
? $next->map(function($item,$key) {
|
||||||
$authorized = FALSE;
|
$authorized = FALSE;
|
||||||
@ -1050,6 +875,22 @@ class Service extends Model implements IDs
|
|||||||
: collect();
|
: collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This service billing charge, pre-taxes
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
public function billing_charge(): float
|
||||||
|
{
|
||||||
|
// If recur_schedule is null, then we only bill this item once
|
||||||
|
if (is_null($this->getBillingIntervalAttribute()) && $this->getInvoicedToAttribute())
|
||||||
|
$this->price = 0;
|
||||||
|
|
||||||
|
return is_null($this->price)
|
||||||
|
? $this->product->getBaseChargeAttribute($this->getBillingIntervalAttribute(),$this->account->group)
|
||||||
|
: $this->price;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the stage parameters
|
* Get the stage parameters
|
||||||
*
|
*
|
||||||
@ -1108,7 +949,8 @@ class Service extends Model implements IDs
|
|||||||
*/
|
*/
|
||||||
public function isActive(): bool
|
public function isActive(): bool
|
||||||
{
|
{
|
||||||
return $this->attributes['active'] || ($this->order_status && (! in_array($this->order_status,self::INACTIVE_STATUS)));
|
return $this->attributes['active']
|
||||||
|
|| ($this->order_status && (! in_array($this->order_status,self::INACTIVE_STATUS)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1180,17 +1022,6 @@ class Service extends Model implements IDs
|
|||||||
return ! is_null($this->price);
|
return ! is_null($this->price);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Should this service be invoiced soon
|
|
||||||
*
|
|
||||||
* @param int $days
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isInvoiceDueSoon($days=30): bool
|
|
||||||
{
|
|
||||||
return $this->isBilled() AND $this->getInvoiceNextAttribute()->lessThan(now()->addDays($days));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identify if a service is being ordered, ie: not active yet nor cancelled
|
* Identify if a service is being ordered, ie: not active yet nor cancelled
|
||||||
*
|
*
|
||||||
@ -1206,95 +1037,88 @@ class Service extends Model implements IDs
|
|||||||
/**
|
/**
|
||||||
* 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
|
||||||
*
|
*
|
||||||
* @param bool $future Next item to be billed (not in the next x days)
|
|
||||||
* @param Carbon|null $billdate
|
* @param Carbon|null $billdate
|
||||||
* @return Collection
|
* @return Collection
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
* @todo This query is expensive.
|
|
||||||
*/
|
*/
|
||||||
public function next_invoice_items(bool $future,Carbon $billdate=NULL): Collection
|
public function next_invoice_items(Carbon $billdate=NULL): Collection
|
||||||
{
|
{
|
||||||
if ($this->wasCancelled() OR (! $this->isBilled()) OR (! $future AND ! $this->active))
|
if ($this->wasCancelled() || (! $this->isBilled()))
|
||||||
return collect();
|
return collect();
|
||||||
|
|
||||||
if (is_null($billdate))
|
$o = collect();
|
||||||
$billdate = Carbon::now()->addDays(30);
|
$invoiced_to = $this->getInvoiceNextAttribute();
|
||||||
|
|
||||||
// If pending, add any connection charges
|
// Connection charges are only charged once, so ignore if if we have already billed them
|
||||||
// Connection charges are only charged once
|
if ((! $this->invoiced_items()->where('item_type',InvoiceItem::INVOICEITEM_SETUP)->count())
|
||||||
if ((! $this->invoice_items->filter(function($item) { return $item->item_type==4; })->sum('total'))
|
&& (InvoiceItem::distinct('invoice_id')->where('service_id',$this->id)->count() < 2)
|
||||||
AND ($this->isPending() OR is_null($this->invoice_to))
|
&& $this->product->getSetupChargeAttribute($this->getBillingIntervalAttribute(),$this->account->group))
|
||||||
AND $this->product->getSetupChargeAttribute($this->recur_schedule,$this->account->group))
|
|
||||||
{
|
{
|
||||||
$o = new InvoiceItem;
|
$ii = new InvoiceItem;
|
||||||
|
|
||||||
$o->active = TRUE;
|
$ii->active = TRUE;
|
||||||
$o->service_id = $this->id;
|
$ii->service_id = $this->id;
|
||||||
$o->product_id = $this->product_id;
|
$ii->product_id = $this->product_id;
|
||||||
$o->item_type = 4; // @todo change to const or something
|
$ii->item_type = InvoiceItem::INVOICEITEM_SETUP;
|
||||||
$o->price_base = $this->product->getSetupChargeAttribute($this->recur_schedule,$this->account->group);
|
$ii->price_base = $this->product->getSetupChargeAttribute($this->getBillingIntervalAttribute(),$this->account->group);
|
||||||
//$o->recurring_schedule = $this->recur_schedule;
|
$ii->start_at = $this->invoice_next;
|
||||||
$o->start_at = $this->invoice_next;
|
$ii->stop_at = $this->invoice_next;
|
||||||
$o->stop_at = $this->invoice_next;
|
$ii->quantity = 1;
|
||||||
$o->quantity = 1;
|
$ii->site_id = 1; // @todo
|
||||||
$o->site_id = 1; // @todo
|
|
||||||
|
|
||||||
$o->addTaxes($this->account->country->taxes);
|
$ii->addTaxes($this->account->country->taxes);
|
||||||
$this->invoice_items->push($o);
|
$o->push($ii);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the service is active, there will be service charges
|
// The service charges
|
||||||
if ((! $this->invoice_items->filter(function($item) { return $item->item_type==0 AND ! $item->exists; })->count())
|
if (is_null($billdate))
|
||||||
AND ($this->active OR $this->isPending())
|
$billdate = $invoiced_to->clone()->addDays(config('osb.invoice_days'));
|
||||||
AND (
|
|
||||||
(($future == TRUE) AND $this->invoice_next < $this->invoice_next_end) OR
|
|
||||||
(($future == FALSE) AND ($this->invoice_to < ($this->stop_at ?: $billdate)))
|
|
||||||
))
|
|
||||||
{
|
|
||||||
do {
|
|
||||||
$o = new InvoiceItem;
|
|
||||||
$o->active = TRUE;
|
|
||||||
$o->service_id = $this->id;
|
|
||||||
$o->product_id = $this->product_id;
|
|
||||||
$o->item_type = 0;
|
|
||||||
$o->price_base = is_null($this->price)
|
|
||||||
? (is_null($this->price) ? $this->product->getBaseChargeAttribute($this->recur_schedule,$this->account->group) : $this->price)
|
|
||||||
: $this->price; // @todo change to a method in this class
|
|
||||||
$o->recur_schedule = $this->recur_schedule;
|
|
||||||
$o->start_at = $this->invoice_next;
|
|
||||||
$o->stop_at = $this->invoice_next_end;
|
|
||||||
$o->quantity = $this->invoice_next_quantity;
|
|
||||||
$o->site_id = 1; // @todo
|
|
||||||
|
|
||||||
$o->addTaxes($this->account->country->taxes);
|
while ($invoiced_to < ($this->stop_at ?: $billdate)) {
|
||||||
$this->invoice_items->push($o);
|
$ii = new InvoiceItem;
|
||||||
} while ($future == FALSE AND ($this->invoice_to < ($this->stop_at ?: $billdate)));
|
$period = Invoice::invoice_period($invoiced_to,$this->getBillingIntervalAttribute(),$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->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);
|
||||||
|
$ii->site_id = 1; // @todo
|
||||||
|
|
||||||
|
$ii->addTaxes($this->account->country->taxes);
|
||||||
|
$o->push($ii);
|
||||||
|
|
||||||
|
$invoiced_to = $ii->stop_at
|
||||||
|
->clone()
|
||||||
|
->addDay()
|
||||||
|
->startOfDay();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add additional charges
|
// Add additional charges
|
||||||
if ((($future == TRUE) OR (($future == FALSE) AND ($this->invoice_to >= $billdate)))
|
foreach ($this->charges->filter(function($item) { return $item->unprocessed; }) as $oo) {
|
||||||
AND ! $this->invoice_items->filter(function($item) { return $item->module_id == 30 AND ! $item->exists; })->count())
|
$ii = new InvoiceItem;
|
||||||
{
|
|
||||||
foreach ($this->charges->filter(function($item) { return $item->unprocessed; }) as $oo) {
|
|
||||||
$o = new InvoiceItem;
|
|
||||||
$o->active = TRUE;
|
|
||||||
$o->service_id = $oo->service_id;
|
|
||||||
$o->product_id = $this->product_id;
|
|
||||||
$o->quantity = $oo->quantity;
|
|
||||||
$o->item_type = $oo->type;
|
|
||||||
$o->price_base = $oo->amount;
|
|
||||||
$o->start_at = $oo->start_at;
|
|
||||||
$o->stop_at = $oo->stop_at;
|
|
||||||
$o->module_id = 30; // @todo This shouldnt be hard coded
|
|
||||||
$o->module_ref = $oo->id;
|
|
||||||
$o->site_id = 1; // @todo
|
|
||||||
|
|
||||||
$o->addTaxes($this->account->country->taxes);
|
$ii->active = TRUE;
|
||||||
$this->invoice_items->push($o);
|
$ii->service_id = $oo->service_id;
|
||||||
}
|
$ii->product_id = $this->product_id;
|
||||||
|
$ii->quantity = $oo->quantity;
|
||||||
|
$ii->item_type = $oo->type;
|
||||||
|
$ii->price_base = $oo->amount;
|
||||||
|
$ii->start_at = $oo->start_at;
|
||||||
|
$ii->stop_at = $oo->stop_at;
|
||||||
|
$ii->module_id = 30; // @todo This shouldnt be hard coded
|
||||||
|
$ii->module_ref = $oo->id;
|
||||||
|
$ii->site_id = 1; // @todo
|
||||||
|
|
||||||
|
$ii->addTaxes($this->account->country->taxes);
|
||||||
|
$o->push($ii);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->invoice_items->filter(function($item) { return ! $item->exists; });
|
return $o;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,11 +48,13 @@ class Broadband extends Type implements ServiceUsage
|
|||||||
* The usage information for broadband
|
* The usage information for broadband
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
|
* @todo rename to usage()
|
||||||
*/
|
*/
|
||||||
public function traffic()
|
public function traffic()
|
||||||
{
|
{
|
||||||
return $this->hasMany(UsageBroadband::class,'service_item_id')
|
return $this->hasMany(UsageBroadband::class,'service_item_id')
|
||||||
->where('site_id',$this->site_id);
|
->where('date','>=',Carbon::now()->startOfMonth());
|
||||||
|
//->where('site_id',$this->site_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ATTRIBUTES */
|
/* ATTRIBUTES */
|
||||||
@ -154,8 +156,6 @@ class Broadband extends Type implements ServiceUsage
|
|||||||
if (! $maxdate)
|
if (! $maxdate)
|
||||||
return collect();
|
return collect();
|
||||||
|
|
||||||
Log::debug(sprintf('%s:Getting Usage data for [%d] months from [%s]',self::LOGKEY,$months,$maxdate),['m'=>__METHOD__]);
|
|
||||||
|
|
||||||
// Go back an extra month;
|
// Go back an extra month;
|
||||||
$start = $maxdate->date->subMonths($months);
|
$start = $maxdate->date->subMonths($months);
|
||||||
|
|
||||||
@ -166,8 +166,6 @@ class Broadband extends Type implements ServiceUsage
|
|||||||
$start = $start->subDays($start->day-15);
|
$start = $start->subDays($start->day-15);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::debug(sprintf('%s:Getting Usage data from [%s]',self::LOGKEY,$start->format('Y-m-d')),['m'=>__METHOD__]);
|
|
||||||
|
|
||||||
$result = collect();
|
$result = collect();
|
||||||
|
|
||||||
foreach ($this->traffic()
|
foreach ($this->traffic()
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Models\Service;
|
namespace App\Models\Service;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Leenooks\Carbon as LeenooksCarbon;
|
use Leenooks\Carbon as LeenooksCarbon;
|
||||||
|
|
||||||
use App\Interfaces\ServiceItem;
|
use App\Interfaces\ServiceItem;
|
||||||
@ -41,6 +42,14 @@ abstract class Type extends Model implements ServiceItem
|
|||||||
return $this->morphOne(Service::class,'type','model','id','service_id');
|
return $this->morphOne(Service::class,'type','model','id','service_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function traffic()
|
||||||
|
{
|
||||||
|
// Return a null relationship by default, if the child class doesnt track usage (and thus no usage table)
|
||||||
|
return $this->hasMany(Generic::class,'id')
|
||||||
|
->where('id',NULL)
|
||||||
|
->where('site_id',$this->site_id);
|
||||||
|
}
|
||||||
|
|
||||||
/* INTERFACE */
|
/* INTERFACE */
|
||||||
|
|
||||||
public function getContractTermAttribute(): int
|
public function getContractTermAttribute(): int
|
||||||
@ -97,4 +106,15 @@ abstract class Type extends Model implements ServiceItem
|
|||||||
{
|
{
|
||||||
return $this->service->offering->supplied ?: new \App\Models\Supplier\Generic();
|
return $this->service->offering->supplied ?: new \App\Models\Supplier\Generic();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default usage summary is empty if the underlying model doesnt capture usage
|
||||||
|
*
|
||||||
|
* @param int $months
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function usage_summary(int $months=2): Collection
|
||||||
|
{
|
||||||
|
return collect();
|
||||||
|
}
|
||||||
}
|
}
|
@ -3,25 +3,23 @@
|
|||||||
namespace App\Models\Usage;
|
namespace App\Models\Usage;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
|
|
||||||
use App\Models\Service\Broadband as ServiceBroadband;
|
use App\Models\Service\Broadband as ServiceBroadband;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class Broadband extends Model
|
class Broadband extends Type
|
||||||
{
|
{
|
||||||
protected $casts = [
|
|
||||||
'date'=>'datetime:Y-m-d',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $table = 'usage_broadband';
|
protected $table = 'usage_broadband';
|
||||||
public $timestamps = FALSE;
|
|
||||||
|
|
||||||
private $traffic_end = 14;
|
private $traffic_end = 14;
|
||||||
|
|
||||||
/* RELATIONS */
|
/* RELATIONS */
|
||||||
|
|
||||||
|
/* @todo rename to service() and put in parent */
|
||||||
|
/* @deprecated */
|
||||||
public function broadband()
|
public function broadband()
|
||||||
{
|
{
|
||||||
|
Log::alert('deprecated function '.__METHOD__);
|
||||||
return $this->belongsTo(ServiceBroadband::class);
|
return $this->belongsTo(ServiceBroadband::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
14
app/Models/Usage/Type.php
Normal file
14
app/Models/Usage/Type.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Usage;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
abstract class Type extends Model
|
||||||
|
{
|
||||||
|
protected $casts = [
|
||||||
|
'date'=>'datetime:Y-m-d',
|
||||||
|
];
|
||||||
|
|
||||||
|
public $timestamps = FALSE;
|
||||||
|
}
|
@ -14,12 +14,23 @@ trait ScopeServiceActive
|
|||||||
*/
|
*/
|
||||||
public function scopeServiceActive($query)
|
public function scopeServiceActive($query)
|
||||||
{
|
{
|
||||||
return $query->where(function($q) {
|
return $query
|
||||||
return $q->where('services.active',TRUE)
|
->where(fn($q)=>
|
||||||
->orWhere(function($q) {
|
$q->where('services.active',TRUE)
|
||||||
return $q->whereNotNull('order_status')
|
->orWhere(fn($q)=>
|
||||||
->whereNotIn('services.order_status',Service::INACTIVE_STATUS);
|
$q->whereNotNull('order_status')
|
||||||
});
|
->whereNotIn('services.order_status',Service::INACTIVE_STATUS))
|
||||||
});
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeServiceInactive($query)
|
||||||
|
{
|
||||||
|
return $query
|
||||||
|
->where(fn($q)=>
|
||||||
|
$q->where('services.active',FALSE)
|
||||||
|
->orWhere(fn($q)=>
|
||||||
|
$q->whereNotNull('order_status')
|
||||||
|
->whereIn('services.order_status',Service::INACTIVE_STATUS))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,5 +3,6 @@
|
|||||||
return [
|
return [
|
||||||
'language_id' => 1,
|
'language_id' => 1,
|
||||||
'invoice_text' => 'Thank you for using our Internet Services.',
|
'invoice_text' => 'Thank you for using our Internet Services.',
|
||||||
|
'invoice_days' => 30, // Days in Advance to invoice
|
||||||
'admin' => env('APP_ADMIN'),
|
'admin' => env('APP_ADMIN'),
|
||||||
];
|
];
|
78
public/plugin/dataTables/leftSearchPanes.css
vendored
78
public/plugin/dataTables/leftSearchPanes.css
vendored
@ -4,55 +4,28 @@ table.dataTable tr.dtrg-group.dtrg-level-1 td {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* RENDERING */
|
/* RENDERING */
|
||||||
|
/* Spacing between sp and table */
|
||||||
div.dtsp-verticalPanes {
|
div.dtsp-verticalPanes {
|
||||||
margin-right: 10px;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.dtsp-panesContainer {
|
div.dtsp-panesContainer {
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
width: 15em;
|
width: 18em;
|
||||||
}
|
|
||||||
|
|
||||||
div.dtsp-subRow1 {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.dtsp-searchCont input.dtsp-search.dtsp-disabledButton {
|
|
||||||
background: #eaeaea;
|
|
||||||
font-size: larger;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.dtsp-searchCont input.dtsp-search.dtsp-disabledButton::placeholder,
|
|
||||||
div.dtsp-searchCont input.dtsp-search.dtsp-disabledButton:-moz-placeholder,
|
|
||||||
div.dtsp-searchCont input.dtsp-search.dtsp-disabledButton::-moz-placeholder,
|
|
||||||
div.dtsp-searchCont input.dtsp-search.dtsp-disabledButton::-webkit-input-placeholder {
|
|
||||||
color: #000000;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div.dtsp-titleRow {
|
div.dtsp-titleRow {
|
||||||
margin-top: 13px;
|
padding: 0.5em;
|
||||||
padding: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div.dtsp-titleRow button {
|
div.dtsp-titleRow button {
|
||||||
padding: 0 0 0 5px !important;
|
padding: 0 0 0 3px !important;
|
||||||
|
margin-bottom: 1px;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.dtsp-titleRow div.dtsp-title {
|
div.dtsp-verticalContainer {
|
||||||
padding: 1px;
|
|
||||||
margin: 0 !important;
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.dtsp-panesContainer div.dtsp-searchPanes div.dtsp-searchPane div.dataTables_scrollBody div.dtsp-nameCont span.dtsp-pill {
|
|
||||||
min-width: 4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.dtsp-verticalContainer{
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@ -62,40 +35,43 @@ div.dtsp-verticalContainer{
|
|||||||
}
|
}
|
||||||
|
|
||||||
div.dtsp-verticalContainer div.dtsp-verticalPanes,
|
div.dtsp-verticalContainer div.dtsp-verticalPanes,
|
||||||
div.dtsp-verticalContainer div.dtsp-dataTable{
|
div.dtsp-verticalContainer div.dtsp-dataTable {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.dtsp-verticalContainer div.dtsp-verticalPanes{
|
div.dtsp-verticalContainer div.dtsp-verticalPanes {
|
||||||
background: rgba(33, 39, 45, 0.1);
|
background: rgba(33, 39, 45, 0.1);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.dtsp-title {
|
/* Fix Search input */
|
||||||
margin-right: 0px !important;
|
div.dtsp-dataTable .dt-search {
|
||||||
margin-top: 13px !important;
|
text-align: right;
|
||||||
margin-left: 5px !important;
|
padding-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.dtsp-search {
|
/* Fix Table Result */
|
||||||
min-width: 0px !important;
|
div.dtsp-dataTable .dt-info {
|
||||||
padding-left: 0px !important;
|
float: left;
|
||||||
margin: 0px !important;
|
padding-top: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.dtsp-verticalContainer div.dtsp-verticalPanes div.dtsp-searchPanes{
|
/* Fix pagination */
|
||||||
flex-direction: column;
|
div.dtsp-dataTable .dt-paging {
|
||||||
flex-basis: 0px;
|
float: right;
|
||||||
|
padding-top: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.dtsp-verticalContainer div.dtsp-verticalPanes div.dtsp-searchPanes div.dtsp-searchPane{
|
/* Titles */
|
||||||
flex-basis: 0px;
|
div.dtsp-panesContainer div.dtsp-searchPane div.dtsp-topRow input.form-control {
|
||||||
|
font-weight: bold;
|
||||||
|
padding-top: 0.5em;
|
||||||
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.dtsp-verticalContainer div.dtsp-dataTable{
|
div.dtsp-verticalContainer div.dtsp-dataTable{
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
@ -8,7 +8,7 @@ Please order the following...
|
|||||||
| Account | {{ $service->account->name }} |
|
| Account | {{ $service->account->name }} |
|
||||||
| Service ID | {{ $service->sid }} |
|
| Service ID | {{ $service->sid }} |
|
||||||
| Product | {{ $service->product->name }} |
|
| Product | {{ $service->product->name }} |
|
||||||
@switch($service->category)
|
@switch($service->product->category)
|
||||||
@case('broadband')
|
@case('broadband')
|
||||||
| Address | {{ $service->type->service_address }} |
|
| Address | {{ $service->type->service_address }} |
|
||||||
@break;
|
@break;
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
| Account | {{ $service->account->name }} |
|
| Account | {{ $service->account->name }} |
|
||||||
| Service ID | {{ $service->sid }} |
|
| Service ID | {{ $service->sid }} |
|
||||||
| Product | {{ $service->product->name }} |
|
| Product | {{ $service->product->name }} |
|
||||||
@switch($service->category)
|
@switch($service->product->category)
|
||||||
@case('broadband')
|
@case('broadband')
|
||||||
| Address | {{ is_object($service->type) ? $service->type->service_address : 'Not Supplied' }} |
|
| Address | {{ is_object($service->type) ? $service->type->service_address : 'Not Supplied' }} |
|
||||||
@break;
|
@break;
|
||||||
|
@ -8,7 +8,7 @@ Please cancel the following...
|
|||||||
| Account | {{ $service->account->name }} |
|
| Account | {{ $service->account->name }} |
|
||||||
| Service ID | {{ $service->sid }} |
|
| Service ID | {{ $service->sid }} |
|
||||||
| Product | {{ $service->product->name }} |
|
| Product | {{ $service->product->name }} |
|
||||||
@switch($service->category)
|
@switch($service->product->category)
|
||||||
@case('broadband')
|
@case('broadband')
|
||||||
| Address | {{ $service->type->service_address }} |
|
| Address | {{ $service->type->service_address }} |
|
||||||
@break;
|
@break;
|
||||||
|
@ -8,7 +8,7 @@ Please change the following...
|
|||||||
| Account | {{ $service->account->name }} |
|
| Account | {{ $service->account->name }} |
|
||||||
| Service ID | {{ $service->sid }} |
|
| Service ID | {{ $service->sid }} |
|
||||||
| Product | {{ $service->product->name }} |
|
| Product | {{ $service->product->name }} |
|
||||||
@switch($service->category)
|
@switch($service->product->category)
|
||||||
@case('broadband')
|
@case('broadband')
|
||||||
| Address | {{ $service->type->service_address }} |
|
| Address | {{ $service->type->service_address }} |
|
||||||
@break;
|
@break;
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
<div class="info-box-content">
|
<div class="info-box-content">
|
||||||
<span class="info-box-text">Active Services</span>
|
<span class="info-box-text">Active Services</span>
|
||||||
<span class="info-box-number">{{ Service::active()->whereIn('account_id',$acts)->count() }} <small>/{{ Service::whereIn('account_id',$acts)->count() }}</small></span>
|
<span class="info-box-number">{{ Service::ServiceActive()->whereIn('account_id',$acts)->count() }} <small>/{{ Service::whereIn('account_id',$acts)->count() }}</small></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
@foreach (\App\Models\Service::active()->with(['product.translate'])->get()->groupBy('product_id') as $s)
|
@foreach (\App\Models\Service::active()->with(['product.translate'])->get()->groupBy('product_id') as $s)
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{ url('a/product/details',[($x=$s->first())->product_id]) }}">{{ $x->id }}</a></td>
|
<td><a href="{{ url('a/product/details',[($x=$s->first())->product_id]) }}">{{ $x->id }}</a></td>
|
||||||
<td>{{ $x->category_name }}</td>
|
<td>{{ $x->product->category_name }}</td>
|
||||||
<td>{{ $x->product->pid }}</td>
|
<td>{{ $x->product->pid }}</td>
|
||||||
<td>{{ $x->product->name }}</td>
|
<td>{{ $x->product->name }}</td>
|
||||||
<td class="text-right">{{ $s->count() }}</td>
|
<td class="text-right">{{ $s->count() }}</td>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@use(App\Models\Service)
|
||||||
|
|
||||||
@extends('adminlte::layouts.app')
|
@extends('adminlte::layouts.app')
|
||||||
|
|
||||||
@section('htmlheader_title')
|
@section('htmlheader_title')
|
||||||
@ -25,21 +27,20 @@
|
|||||||
<th>Product</th>
|
<th>Product</th>
|
||||||
<th class="text-right">Monthly</th>
|
<th class="text-right">Monthly</th>
|
||||||
<th class="text-right">Cost</th>
|
<th class="text-right">Cost</th>
|
||||||
<th class="text-right">Traffic (GB)</th>
|
<th class="text-right">Usage</th>
|
||||||
<th>Supplier</th>
|
<th>Supplier</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{{-- @todo This query is expensive still --}}
|
@foreach (Service::ServiceActive()->with(['account.taxes','type','product.type.supplied.supplier_detail.supplier','product.translate','type.traffic'])->get() as $o)
|
||||||
@foreach (\App\Models\Service::active()->with(['type','product.type.supplied.supplier_detail.supplier','product.translate'])->get() as $o)
|
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{ url('u/service',[$o->id]) }}">{{ $o->id }}</a></td>
|
<td><a href="{{ url('u/service',[$o->id]) }}">{{ $o->id }}</a></td>
|
||||||
<td>{{ $o->name }}</td>
|
<td>{{ $o->name }}</td>
|
||||||
<td>{{ $o->product->name }}</td>
|
<td>{{ $o->product->name }}</td>
|
||||||
<td class="text-right">{{ number_format($o->billing_monthly_price,2) }}</td>
|
<td class="text-right">{{ number_format($o->billing_charge_normalised,2) }}</td>
|
||||||
<td class="text-right">{{ number_format($o->product->cost_normalized(),2) }}</td>
|
<td class="text-right">{{ number_format($o->product->cost_normalized(),2) }}</td>
|
||||||
<td class="text-right">{{ $o->category == 'broadband' ? 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>
|
||||||
@endforeach
|
@endforeach
|
||||||
@ -50,51 +51,60 @@
|
|||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
@pa(datatables,rowgroup|conditionalpaging|select|searchpanes|searchpanes-left)
|
||||||
|
|
||||||
@section('page-scripts')
|
@section('page-scripts')
|
||||||
@css(datatables,bootstrap4|fixedheader|responsive|rowgroup|buttons)
|
|
||||||
@js(datatables,bootstrap4|fixedheader|responsive|rowgroup|buttons)
|
|
||||||
|
|
||||||
<style>
|
|
||||||
tr.odd td:first-child,
|
|
||||||
tr.even td:first-child {
|
|
||||||
padding-left: 3em;
|
|
||||||
}
|
|
||||||
table.dataTable tr.dtrg-group.dtrg-level-1 td {
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
color: #4c110f;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('#table').DataTable({
|
$('#table').DataTable({
|
||||||
//oSearch: { sSearch: searchString ? decodeURIComponent(searchString) : '' },
|
//oSearch: { sSearch: searchString ? decodeURIComponent(searchString) : '' },
|
||||||
aLengthMenu: [
|
aLengthMenu: [
|
||||||
[25, 50, 100, 200, -1],
|
[25, 50, 100, 200, -1],
|
||||||
[25, 50, 100, 200, "All"]
|
[25, 50, 100, 200, "All"]
|
||||||
],
|
],
|
||||||
paging: true,
|
paging: true,
|
||||||
pageLength: 25,
|
pageLength: 25,
|
||||||
lengthChange: true,
|
conditionalPaging: true,
|
||||||
searching: true,
|
lengthChange: true,
|
||||||
ordering: true,
|
searching: true,
|
||||||
info: true,
|
ordering: true,
|
||||||
autoWidth: false,
|
info: true,
|
||||||
fixedHeader: true,
|
autoWidth: false,
|
||||||
order: [
|
fixedHeader: true,
|
||||||
[2,'asc'],
|
order: [
|
||||||
[1,'asc'],
|
[2,'asc'],
|
||||||
],
|
[1,'asc'],
|
||||||
rowGroup: {
|
],
|
||||||
dataSrc: [2],
|
rowGroup: {
|
||||||
},
|
dataSrc: [2],
|
||||||
columnDefs: [
|
},
|
||||||
{
|
columnDefs: [
|
||||||
targets: [2],
|
{
|
||||||
visible: false,
|
targets: [2],
|
||||||
},
|
visible: false,
|
||||||
],
|
},
|
||||||
});
|
{
|
||||||
});
|
targets: [0,1,3,4,5],
|
||||||
</script>
|
searchPanes: {
|
||||||
|
show: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
language: {
|
||||||
|
searchPanes: {
|
||||||
|
title: 'Filters: %d',
|
||||||
|
collapse: 'Filter',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
searchPanes: {
|
||||||
|
cascadePanes: true,
|
||||||
|
viewTotal: true,
|
||||||
|
layout: 'columns-1',
|
||||||
|
dataLength: 20,
|
||||||
|
controls: false,
|
||||||
|
},
|
||||||
|
dom: '<"dtsp-verticalContainer"<"dtsp-verticalPanes"P><"dtsp-dataTable"Bfrtip>>',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@append
|
@append
|
@ -8,11 +8,11 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="card-header bg-light">
|
<div class="card-header bg-light p-2">
|
||||||
<h3 class="card-title">Service Information</h3>
|
<h3 class="card-title">Service Information</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body bg-light">
|
<div class="card-body p-2">
|
||||||
<table class="table table-sm">
|
<table class="table table-sm">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Account</th>
|
<th>Account</th>
|
||||||
@ -28,7 +28,7 @@
|
|||||||
<td>{{ $o->order_info_reference ?? '' }}</td>
|
<td>{{ $o->order_info_reference ?? '' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endif
|
@endif
|
||||||
@if($o->start_at AND $o->isPending())
|
@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>
|
||||||
@ -47,10 +47,10 @@
|
|||||||
<td>${{ number_format($o->billing_charge,2) }}</td>
|
<td>${{ number_format($o->billing_charge,2) }}</td>
|
||||||
@endif
|
@endif
|
||||||
</tr>
|
</tr>
|
||||||
@if($o->active && $o->invoice_to)
|
@if($o->isActive() && $o->invoiced_to)
|
||||||
<tr>
|
<tr>
|
||||||
<th>Invoiced To</th>
|
<th>Invoiced To</th>
|
||||||
<td>{{ $o->invoice_to->format('Y-m-d') }}</td>
|
<td>{{ $o->invoiced_to->format('Y-m-d') }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@if($o->paid_to)
|
@if($o->paid_to)
|
||||||
<tr>
|
<tr>
|
||||||
@ -59,14 +59,16 @@
|
|||||||
</tr>
|
</tr>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
@if($o->status !== 'cancel-pending')
|
||||||
<tr>
|
<tr>
|
||||||
<th>Next Invoice</th>
|
<th>Next Invoice</th>
|
||||||
<td>@if($o->suspend_billing)<del>@endif{{ $o->invoice_next->format('Y-m-d') }}@if($o->suspend_billing)</del> <strong>SUSPENDED</strong>@endif</td>
|
<td>@if($o->suspend_billing)<del>@endif{{ $o->invoice_next->format('Y-m-d') }}@if($o->suspend_billing)</del> <strong>SUSPENDED</strong>@endif</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Next Estimated Invoice</th>
|
<th>Next Estimated Invoice</th>
|
||||||
<td>${{ number_format($o->next_invoice_items(TRUE)->sum('total'),2) }} <sup>*</sup></td>
|
<td>${{ number_format($o->next_invoice_items()->sum('total'),2) }} <sup>*</sup></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@endif
|
||||||
<tr>
|
<tr>
|
||||||
<th>Payment Method</th>
|
<th>Payment Method</th>
|
||||||
<td>@if($o->billing)Direct Debit @else Invoice @endif</td>
|
<td>@if($o->billing)Direct Debit @else Invoice @endif</td>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<!-- $o=Service::class, $p=Product::class -->
|
@use(App\Models\Invoice)
|
||||||
@php($c=$o->product)
|
@php($c=$o->product)
|
||||||
|
|
||||||
|
<!-- $o=Service::class, $p=Product::class -->
|
||||||
<table class="table table-sm">
|
<table class="table table-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -75,16 +77,16 @@
|
|||||||
<th>Monthly Price</th>
|
<th>Monthly Price</th>
|
||||||
<td @if($x=$o->isChargeOverridden()) class="text-danger" @endif>
|
<td @if($x=$o->isChargeOverridden()) class="text-danger" @endif>
|
||||||
@if($x)
|
@if($x)
|
||||||
<abbr title="${{ number_format($b=$o->account->taxed($c->base_charge)*\App\Models\Invoice::billing_change($o->billing_interval,\App\Models\Invoice::BILL_MONTHLY),2) }}">${{ number_format($b=$o->billing_monthly_price,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->account->taxed($c->base_charge)*\App\Models\Invoice::billing_change($o->billing_interval,\App\Models\Invoice::BILL_MONTHLY),2) }}
|
{{ number_format($b=$o->billing_charge_normalised,2) }}
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td>${{ number_format($a=$o->account->taxed($c->base_cost)*\App\Models\Invoice::billing_change($o->billing_interval,\App\Models\Invoice::BILL_MONTHLY),2) }}</td>
|
<td>${{ number_format($a=$o->account->taxed($c->base_cost)*Invoice::billing_change($o->billing_interval,Invoice::BILL_MONTHLY),2) }}</td>
|
||||||
<td>{!! markup($a,$b) !!}</td>
|
<td>{!! markup($a,$b) !!}</td>
|
||||||
@if($p->exists)
|
@if($p->exists)
|
||||||
<td @if($x=$o->isChargeOverridden()) class="text-danger" @endif>${{ number_format($b=$o->account->taxed($p->base_charge)*\App\Models\Invoice::billing_change($o->billing_interval,\App\Models\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>${{ number_format($a=$o->account->taxed($p->base_cost)*\App\Models\Invoice::billing_change($o->billing_interval,\App\Models\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>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="3">{{ $o->name }}</th><th class="text-right">${{ number_format(($x=$o->next_invoice_items(TRUE))->sum('total'),2) }}</th>
|
<th colspan="3">{{ $o->name }}</th><th class="text-right">${{ number_format(($x=$o->next_invoice_items())->sum('total'),2) }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@foreach ($x as $io)
|
@foreach ($x as $io)
|
||||||
|
@ -121,7 +121,7 @@ Route::group(['middleware'=>['auth','role:wholesaler'],'prefix'=>'a'],function()
|
|||||||
|
|
||||||
Route::get('report/accounts',[ReportController::class,'accounts']);
|
Route::get('report/accounts',[ReportController::class,'accounts']);
|
||||||
Route::get('report/products',[ReportController::class,'products']);
|
Route::get('report/products',[ReportController::class,'products']);
|
||||||
Route::get('report/services',[ReportController::class,'services']);
|
Route::view('report/services','theme.backend.adminlte.service.report');
|
||||||
|
|
||||||
// Payments - @todo This should probably go to resellers
|
// Payments - @todo This should probably go to resellers
|
||||||
Route::match(['get','post'],'payment/addedit/{o?}',[AdminController::class,'pay_addedit']);
|
Route::match(['get','post'],'payment/addedit/{o?}',[AdminController::class,'pay_addedit']);
|
||||||
@ -172,9 +172,6 @@ Route::group(['middleware'=>['auth'],'prefix'=>'u'],function() {
|
|||||||
Route::get('home/{o}',[HomeController::class,'home'])
|
Route::get('home/{o}',[HomeController::class,'home'])
|
||||||
->where('o','[0-9]+')
|
->where('o','[0-9]+')
|
||||||
->middleware('can:view,o');
|
->middleware('can:view,o');
|
||||||
// Route::get('account/{o}/invoice','User\AccountController@view_invoice_next')
|
|
||||||
// ->where('o','[0-9]+')
|
|
||||||
// ->middleware('can:view,o');
|
|
||||||
Route::post('checkout/pay',[CheckoutController::class,'pay']);
|
Route::post('checkout/pay',[CheckoutController::class,'pay']);
|
||||||
Route::get('invoice/{o}',[InvoiceController::class,'view'])
|
Route::get('invoice/{o}',[InvoiceController::class,'view'])
|
||||||
->where('o','[0-9]+')
|
->where('o','[0-9]+')
|
||||||
|
Loading…
Reference in New Issue
Block a user