From 0b5bc9e01232520a97a4933ec5019f3480233fc9 Mon Sep 17 00:00:00 2001 From: Deon George Date: Mon, 29 Jul 2024 23:12:53 +1000 Subject: [PATCH] Rework service, removed redundant code, service invoicing improvements --- app/Console/Commands/ServiceList.php | 38 +- app/Http/Controllers/SearchController.php | 8 +- app/Http/Controllers/ServiceController.php | 20 +- .../Controllers/User/AccountController.php | 44 -- .../Wholesale/ReportController.php | 5 - app/Jobs/ImportCosts.php | 6 +- app/Mail/OrderRequestApprove.php | 14 +- app/Models/Account.php | 2 +- app/Models/Invoice.php | 142 +++++++ app/Models/InvoiceItem.php | 7 +- app/Models/Service.php | 394 +++++------------- app/Models/Service/Broadband.php | 8 +- app/Models/Service/Type.php | 20 + app/Models/Usage/Broadband.php | 12 +- app/Models/Usage/Type.php | 14 + app/Traits/ScopeServiceActive.php | 25 +- config/osb.php | 1 + public/plugin/dataTables/leftSearchPanes.css | 80 ++-- .../views/email/admin/order/approve.blade.php | 2 +- .../views/email/admin/order/reject.blade.php | 2 +- .../email/admin/service/cancel.blade.php | 2 +- .../email/admin/service/change.blade.php | 2 +- .../account/widget/summary_boxes.blade.php | 2 +- .../backend/adminlte/product/report.blade.php | 2 +- .../backend/adminlte/service/report.blade.php | 110 ++--- .../service/widget/information.blade.php | 14 +- .../service/widget/internal.blade.php | 14 +- .../adminlte/service/widget/invoice.blade.php | 2 +- routes/web.php | 5 +- 29 files changed, 474 insertions(+), 523 deletions(-) delete mode 100644 app/Http/Controllers/User/AccountController.php create mode 100644 app/Models/Usage/Type.php diff --git a/app/Console/Commands/ServiceList.php b/app/Console/Commands/ServiceList.php index 7e0330f..90b9864 100644 --- a/app/Console/Commands/ServiceList.php +++ b/app/Console/Commands/ServiceList.php @@ -3,9 +3,8 @@ namespace App\Console\Commands; use Illuminate\Console\Command; -use Illuminate\Support\Facades\Config; -use App\Models\{Service,Site}; +use App\Models\Service; class ServiceList extends Command { @@ -33,7 +32,7 @@ class ServiceList extends Command */ 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, 'ID', @@ -42,41 +41,42 @@ class ServiceList extends Command 'Name', 'Active', 'Status', - 'Next Invoice', - 'Start Date', - 'Stop Date', - 'Connect Date', - 'First Invoice' + 'Start', + 'Stop', + 'Connect', + 'First', + 'Next', )); - foreach (Service::withoutGlobalScope(\App\Models\Scopes\SiteScope::class)->with(['site'])->cursor() as $o) { - if ((! $this->option('inactive')) AND ! $o->isActive()) + foreach (Service::cursor() as $o) { + if ((! $this->option('inactive')) && (! $o->isActive())) continue; - Config::set('site',$o->site); - - if ($this->option('type') AND ($o->product->getCategoryAttribute() !== $this->option('type'))) + if ($this->option('type') && ($o->product->getCategoryAttribute() !== $this->option('type'))) 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->save(); } $this->info(sprintf($header, - $o->sid, + $o->lid, $o->product->getCategoryNameAttribute(), substr($o->product->getNameAttribute(),0,35), substr($o->name_short,0,40), $o->active ? 'active' : 'inactive', $o->status, - $o->invoice_next?->format('Y-m-d'), $o->start_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, - ($c && $c->date_start) ? $c->date_start->format('Y-m-d') : NULL, + ($o->type && $o->type->connect_at) ? $o->type->connect_at->format('Y-m-d') : NULL, + ($c && $c->start_at) ? $c->start_at->format('Y-m-d') : NULL, + $o->invoice_next?->format('Y-m-d'), )); } } diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index b9a233c..13b06e5 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -68,9 +68,11 @@ class SearchController extends Controller foreach (Service::Search($request->input('term')) ->whereIN('account_id',$account_ids) ->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 ? '' : 'INACT'),'value'=>'/u/service/'.$o->id,'category'=>$o->category_name]); + $result->push(['name'=>sprintf('%s (%s) %s',$o->name,$o->lid,$o->active ? '' : 'INACT'),'value'=>'/u/service/'.$o->id,'category'=>$o->product->category_name]); } // Look for an Invoice @@ -93,7 +95,7 @@ class SearchController extends Controller } return $result - ->sortBy(function($item) { return $item['category'].$item['name']; }) + ->sortBy(fn($item)=>$item['category'].$item['name']) ->values(); } } \ No newline at end of file diff --git a/app/Http/Controllers/ServiceController.php b/app/Http/Controllers/ServiceController.php index 0e22f26..b00e19a 100644 --- a/app/Http/Controllers/ServiceController.php +++ b/app/Http/Controllers/ServiceController.php @@ -333,7 +333,7 @@ class ServiceController extends Controller $start_at = Carbon::create(Arr::get($request->broadband,'start_at')); // 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); }) as $iio) { @@ -351,7 +351,7 @@ class ServiceController extends Controller $co->stop_at = $iio->stop_at; $co->amount = $iio->price_base; $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); // Add the new charge @@ -368,7 +368,7 @@ class ServiceController extends Controller $co->stop_at = $iio->stop_at; $co->amount = Arr::get($request->broadband,'price') ?: $po->base_charge; $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); } @@ -424,10 +424,10 @@ class ServiceController extends Controller $request->post(), $x=collect($o->type->validation()) ->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())) ->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) ->merge( [ @@ -436,7 +436,7 @@ class ServiceController extends Controller 'recur_schedule' => ['required',Rule::in(collect(Invoice::billing_periods)->keys())], 'invoice_next_at' => 'nullable|date', 'price' => 'nullable|numeric', - $o->category => 'array|min:1', + $o->product->category => 'array|min:1', ] ) ->toArray() @@ -452,13 +452,13 @@ class ServiceController extends Controller $validated = collect($validator->validated()); // Store our service type values - $o->type->forceFill($validated->get($o->category)); + $o->type->forceFill($validated->get($o->product->category)); // Some special handling - switch ($o->category) { + switch ($o->product->category) { case 'broadband': // 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) { $o->type->service_username = NULL; @@ -487,7 +487,7 @@ class ServiceController extends Controller else { // For broadband, start_at is connect_at in the type record - switch ($o->category) { + switch ($o->product->category) { case 'broadband': $o->start_at = $o->type->connect_at; break; diff --git a/app/Http/Controllers/User/AccountController.php b/app/Http/Controllers/User/AccountController.php deleted file mode 100644 index 92bc164..0000000 --- a/app/Http/Controllers/User/AccountController.php +++ /dev/null @@ -1,44 +0,0 @@ -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); - } -} \ No newline at end of file diff --git a/app/Http/Controllers/Wholesale/ReportController.php b/app/Http/Controllers/Wholesale/ReportController.php index 20f8c50..e9d103f 100644 --- a/app/Http/Controllers/Wholesale/ReportController.php +++ b/app/Http/Controllers/Wholesale/ReportController.php @@ -15,9 +15,4 @@ class ReportController extends Controller { return view('product/report'); } - - public function services() - { - return view('service/report'); - } } \ No newline at end of file diff --git a/app/Jobs/ImportCosts.php b/app/Jobs/ImportCosts.php index e7aaa08..b7a6a4a 100644 --- a/app/Jobs/ImportCosts.php +++ b/app/Jobs/ImportCosts.php @@ -131,7 +131,7 @@ class ImportCosts implements ShouldQueue if ($so) { // r[1] = Monthly Charge or Extra Charge,r[2] = "On Plan", r[3] = Plan Info $r = []; - switch ($so->category) { + switch ($so->product->category) { case 'broadband': $to = Cost\Broadband::where('site_id',$this->co->site_id) ->where('cost_id',$this->co->id) @@ -192,8 +192,8 @@ class ImportCosts implements ShouldQueue break; default: - dump(['so'=>$so,'category'=>$so->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)); + 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->product->category,$c)); } } else { diff --git a/app/Mail/OrderRequestApprove.php b/app/Mail/OrderRequestApprove.php index b181bd1..3081701 100644 --- a/app/Mail/OrderRequestApprove.php +++ b/app/Mail/OrderRequestApprove.php @@ -14,7 +14,7 @@ class OrderRequestApprove extends Mailable { use Queueable, SerializesModels; - public Service $service; + public Service $so; public string $notes; /** @@ -25,7 +25,7 @@ class OrderRequestApprove extends Mailable */ public function __construct(Service $o,string $notes='') { - $this->service = $o; + $this->so = $o; $this->notes = $notes; } @@ -36,14 +36,14 @@ class OrderRequestApprove extends Mailable */ public function build() { - Config::set('site',$this->service->site); + Config::set('site',$this->so->site); // @todo This is not consistent with Cancel/Change Request - switch ($this->service->category) { - case 'broadband': $subject = sprintf('%s: %s',$this->service->category,$this->service->type->service_address); + switch ($this->so->product->category) { + case 'broadband': $subject = sprintf('%s: %s',$this->so->product->category,$this->so->type->service_address); 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; default: @@ -53,6 +53,6 @@ class OrderRequestApprove extends Mailable return $this ->markdown('email.admin.order.approve') ->subject($subject) - ->with(['site'=>$this->service->site]); + ->with(['site'=>$this->so->site]); } } \ No newline at end of file diff --git a/app/Models/Account.php b/app/Models/Account.php index 6db2df6..63a5fa0 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -156,7 +156,7 @@ class Account extends Model implements IDs public function services_active() { return $this->services() - ->active(); + ->ServiceActive(); } /** diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 323f71a..cb5a583 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -158,6 +158,148 @@ class Invoice extends Model implements IDs 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 */ /** diff --git a/app/Models/InvoiceItem.php b/app/Models/InvoiceItem.php index c1a9abb..49a726c 100644 --- a/app/Models/InvoiceItem.php +++ b/app/Models/InvoiceItem.php @@ -30,13 +30,16 @@ class InvoiceItem extends Model // Array of items that can be updated with PushNew 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 public const type = [ - 0 => 'Service Charge', + self::INVOICEITEM_SERVICE => 'Service Charge', 1 => 'Hardware', // * 2 => 'Service Relocation Fee', // * 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 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 diff --git a/app/Models/Service.php b/app/Models/Service.php index efc3b82..d285ca8 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -16,7 +16,7 @@ use Leenooks\Casts\LeenooksCarbon; use App\Models\Product\Type; use App\Interfaces\IDs; -use App\Traits\ScopeServiceUserAuthorised; +use App\Traits\{ScopeServiceActive,ScopeServiceUserAuthorised}; /** * Class Service @@ -29,10 +29,10 @@ use App\Traits\ScopeServiceUserAuthorised; * * Attributes for services: * + 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_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_name : The type of service this is, eg: Broadband, Telephone (in human friendly) * + contract_term : The term that this service must be active @@ -54,7 +54,7 @@ use App\Traits\ScopeServiceUserAuthorised; */ class Service extends Model implements IDs { - use HasFactory,ScopeServiceUserAuthorised; + use HasFactory,ScopeServiceActive,ScopeServiceUserAuthorised; protected $casts = [ 'order_info' => AsCollection::class, @@ -281,8 +281,8 @@ class Service extends Model implements IDs public static function movements(User $uo): Collection { return (new self) - ->active() - ->serviceUserAuthorised($uo) + ->ServiceActive() + ->ServiceUserAuthorised($uo) ->where('order_status','!=','ACTIVE') ->with(['account','product']) ->get(); @@ -343,7 +343,7 @@ class Service extends Model implements IDs public function charges_active() { return $this->charges() - ->active(); + ->ServiceActive(); } /** @@ -371,6 +371,7 @@ class Service extends Model implements IDs */ public function invoice_items($active=TRUE) { + Log::alert('Call to deprecated functon '.__METHOD__); return $this->invoiced_items_active(); } @@ -418,6 +419,7 @@ class Service extends Model implements IDs { return $this->hasMany(InvoiceItem::class) ->where('item_type','=',0) + ->whereNotNull('start_at') ->orderBy('start_at','desc'); } @@ -464,9 +466,11 @@ class Service extends Model implements IDs /** * 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) @@ -479,9 +483,11 @@ class Service extends Model implements IDs * * @param $query * @return mixed + * @deprecated use ScopeServiceInactive */ - public function scopeInActive($query) + public function scopeInactive($query) { + dd('deprecated'); return $query->where( fn($query)=> $query->where($this->getTable().'.active',FALSE) @@ -527,15 +533,7 @@ class Service extends Model implements IDs */ public function getBillingChargeAttribute(): float { - // If recur_schedule is null, then we only bill this item once - 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 - ); + return $this->account->taxed($this->billing_charge()); } /** @@ -546,7 +544,7 @@ class Service extends Model implements IDs */ 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()); } - /** - * 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 * @@ -652,155 +617,19 @@ class Service extends Model implements IDs */ public function getInvoiceNextAttribute(): Carbon { - $last = $this->getInvoiceToAttribute(); + $last = $this->getInvoicedToAttribute(); return $last ? $last->addDay() : (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 * * @return Carbon|null */ - public function getInvoiceToAttribute(): ?Carbon + public function getInvoicedToAttribute(): ?Carbon { return ($x=$this->invoiced_service_items_active_recent)->count() ? $x->first()->stop_at @@ -826,7 +655,9 @@ class Service extends Model implements IDs */ 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() { - 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(); 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; } - /** - * 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 * @@ -1033,6 +857,7 @@ class Service extends Model implements IDs public function actions(): Collection { $next = $this->getStageParameters($this->order_status)->get('next'); + return $next ? $next->map(function($item,$key) { $authorized = FALSE; @@ -1050,6 +875,22 @@ class Service extends Model implements IDs : 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 * @@ -1108,7 +949,8 @@ class Service extends Model implements IDs */ 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); } - /** - * 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 * @@ -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 * - * @param bool $future Next item to be billed (not in the next x days) * @param Carbon|null $billdate * @return Collection * @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(); - if (is_null($billdate)) - $billdate = Carbon::now()->addDays(30); + $o = collect(); + $invoiced_to = $this->getInvoiceNextAttribute(); - // If pending, add any connection charges - // Connection charges are only charged once - if ((! $this->invoice_items->filter(function($item) { return $item->item_type==4; })->sum('total')) - AND ($this->isPending() OR is_null($this->invoice_to)) - AND $this->product->getSetupChargeAttribute($this->recur_schedule,$this->account->group)) + // Connection charges are only charged once, so ignore if if we have already billed them + if ((! $this->invoiced_items()->where('item_type',InvoiceItem::INVOICEITEM_SETUP)->count()) + && (InvoiceItem::distinct('invoice_id')->where('service_id',$this->id)->count() < 2) + && $this->product->getSetupChargeAttribute($this->getBillingIntervalAttribute(),$this->account->group)) { - $o = new InvoiceItem; + $ii = new InvoiceItem; - $o->active = TRUE; - $o->service_id = $this->id; - $o->product_id = $this->product_id; - $o->item_type = 4; // @todo change to const or something - $o->price_base = $this->product->getSetupChargeAttribute($this->recur_schedule,$this->account->group); - //$o->recurring_schedule = $this->recur_schedule; - $o->start_at = $this->invoice_next; - $o->stop_at = $this->invoice_next; - $o->quantity = 1; - $o->site_id = 1; // @todo + $ii->active = TRUE; + $ii->service_id = $this->id; + $ii->product_id = $this->product_id; + $ii->item_type = InvoiceItem::INVOICEITEM_SETUP; + $ii->price_base = $this->product->getSetupChargeAttribute($this->getBillingIntervalAttribute(),$this->account->group); + $ii->start_at = $this->invoice_next; + $ii->stop_at = $this->invoice_next; + $ii->quantity = 1; + $ii->site_id = 1; // @todo - $o->addTaxes($this->account->country->taxes); - $this->invoice_items->push($o); + $ii->addTaxes($this->account->country->taxes); + $o->push($ii); } - // If the service is active, there will be service charges - if ((! $this->invoice_items->filter(function($item) { return $item->item_type==0 AND ! $item->exists; })->count()) - AND ($this->active OR $this->isPending()) - 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 + // The service charges + if (is_null($billdate)) + $billdate = $invoiced_to->clone()->addDays(config('osb.invoice_days')); - $o->addTaxes($this->account->country->taxes); - $this->invoice_items->push($o); - } while ($future == FALSE AND ($this->invoice_to < ($this->stop_at ?: $billdate))); + while ($invoiced_to < ($this->stop_at ?: $billdate)) { + $ii = new InvoiceItem; + $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 - if ((($future == TRUE) OR (($future == FALSE) AND ($this->invoice_to >= $billdate))) - AND ! $this->invoice_items->filter(function($item) { return $item->module_id == 30 AND ! $item->exists; })->count()) - { - 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 + foreach ($this->charges->filter(function($item) { return $item->unprocessed; }) as $oo) { + $ii = new InvoiceItem; - $o->addTaxes($this->account->country->taxes); - $this->invoice_items->push($o); - } + $ii->active = TRUE; + $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; } /** diff --git a/app/Models/Service/Broadband.php b/app/Models/Service/Broadband.php index 3874c03..62cfa75 100644 --- a/app/Models/Service/Broadband.php +++ b/app/Models/Service/Broadband.php @@ -48,11 +48,13 @@ class Broadband extends Type implements ServiceUsage * The usage information for broadband * * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @todo rename to usage() */ public function traffic() { 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 */ @@ -154,8 +156,6 @@ class Broadband extends Type implements ServiceUsage if (! $maxdate) 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; $start = $maxdate->date->subMonths($months); @@ -166,8 +166,6 @@ class Broadband extends Type implements ServiceUsage $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(); foreach ($this->traffic() diff --git a/app/Models/Service/Type.php b/app/Models/Service/Type.php index 599fa78..45b0847 100644 --- a/app/Models/Service/Type.php +++ b/app/Models/Service/Type.php @@ -3,6 +3,7 @@ namespace App\Models\Service; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Collection; use Leenooks\Carbon as LeenooksCarbon; 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'); } + 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 */ 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(); } + + /** + * 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(); + } } \ No newline at end of file diff --git a/app/Models/Usage/Broadband.php b/app/Models/Usage/Broadband.php index 4f9632c..1f1e639 100644 --- a/app/Models/Usage/Broadband.php +++ b/app/Models/Usage/Broadband.php @@ -3,25 +3,23 @@ namespace App\Models\Usage; use Carbon\Carbon; -use Illuminate\Database\Eloquent\Model; 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'; - public $timestamps = FALSE; private $traffic_end = 14; /* RELATIONS */ + /* @todo rename to service() and put in parent */ + /* @deprecated */ public function broadband() { + Log::alert('deprecated function '.__METHOD__); return $this->belongsTo(ServiceBroadband::class); } diff --git a/app/Models/Usage/Type.php b/app/Models/Usage/Type.php new file mode 100644 index 0000000..a2827a2 --- /dev/null +++ b/app/Models/Usage/Type.php @@ -0,0 +1,14 @@ +'datetime:Y-m-d', + ]; + + public $timestamps = FALSE; +} \ No newline at end of file diff --git a/app/Traits/ScopeServiceActive.php b/app/Traits/ScopeServiceActive.php index 35eddc3..944c910 100644 --- a/app/Traits/ScopeServiceActive.php +++ b/app/Traits/ScopeServiceActive.php @@ -14,12 +14,23 @@ trait ScopeServiceActive */ public function scopeServiceActive($query) { - return $query->where(function($q) { - return $q->where('services.active',TRUE) - ->orWhere(function($q) { - return $q->whereNotNull('order_status') - ->whereNotIn('services.order_status',Service::INACTIVE_STATUS); - }); - }); + return $query + ->where(fn($q)=> + $q->where('services.active',TRUE) + ->orWhere(fn($q)=> + $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)) + ); } } diff --git a/config/osb.php b/config/osb.php index dc2aa7a..dcede2c 100644 --- a/config/osb.php +++ b/config/osb.php @@ -3,5 +3,6 @@ return [ 'language_id' => 1, 'invoice_text' => 'Thank you for using our Internet Services.', + 'invoice_days' => 30, // Days in Advance to invoice 'admin' => env('APP_ADMIN'), ]; \ No newline at end of file diff --git a/public/plugin/dataTables/leftSearchPanes.css b/public/plugin/dataTables/leftSearchPanes.css index 52ae593..2b07579 100644 --- a/public/plugin/dataTables/leftSearchPanes.css +++ b/public/plugin/dataTables/leftSearchPanes.css @@ -4,55 +4,28 @@ table.dataTable tr.dtrg-group.dtrg-level-1 td { } /* RENDERING */ +/* Spacing between sp and table */ div.dtsp-verticalPanes { - margin-right: 10px; + margin-right: 1em; } div.dtsp-panesContainer { margin-top: 0px; margin-bottom: 0px; - width: 15em; -} - -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; + width: 18em; } div.dtsp-titleRow { - margin-top: 13px; - padding: 5px; + padding: 0.5em; } div.dtsp-titleRow button { - padding: 0 0 0 5px !important; + padding: 0 0 0 3px !important; + margin-bottom: 1px; font-size: 90%; } -div.dtsp-titleRow div.dtsp-title { - 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{ +div.dtsp-verticalContainer { display: flex; flex-direction: row; flex-wrap: wrap; @@ -62,42 +35,45 @@ div.dtsp-verticalContainer{ } div.dtsp-verticalContainer div.dtsp-verticalPanes, -div.dtsp-verticalContainer div.dtsp-dataTable{ +div.dtsp-verticalContainer div.dtsp-dataTable { width: 50%; flex-grow: 0; flex-shrink: 0; flex-basis: 0; } -div.dtsp-verticalContainer div.dtsp-verticalPanes{ +div.dtsp-verticalContainer div.dtsp-verticalPanes { background: rgba(33, 39, 45, 0.1); border-radius: 6px; - border: 1px solid #ccc; + border: 1px solid #aaa; } -div.dtsp-title { - margin-right: 0px !important; - margin-top: 13px !important; - margin-left: 5px !important; +/* Fix Search input */ +div.dtsp-dataTable .dt-search { + text-align: right; + padding-bottom: 0.5em; } -input.dtsp-search { - min-width: 0px !important; - padding-left: 0px !important; - margin: 0px !important; +/* Fix Table Result */ +div.dtsp-dataTable .dt-info { + float: left; + padding-top: 0.75em; } -div.dtsp-verticalContainer div.dtsp-verticalPanes div.dtsp-searchPanes{ - flex-direction: column; - flex-basis: 0px; +/* Fix pagination */ +div.dtsp-dataTable .dt-paging { + float: right; + padding-top: 0.5em; } -div.dtsp-verticalContainer div.dtsp-verticalPanes div.dtsp-searchPanes div.dtsp-searchPane{ - flex-basis: 0px; +/* Titles */ +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{ flex-grow: 1; flex-shrink: 0; flex-basis: auto; -} +} \ No newline at end of file diff --git a/resources/views/email/admin/order/approve.blade.php b/resources/views/email/admin/order/approve.blade.php index 12d8137..bf59857 100644 --- a/resources/views/email/admin/order/approve.blade.php +++ b/resources/views/email/admin/order/approve.blade.php @@ -8,7 +8,7 @@ Please order the following... | Account | {{ $service->account->name }} | | Service ID | {{ $service->sid }} | | Product | {{ $service->product->name }} | -@switch($service->category) +@switch($service->product->category) @case('broadband') | Address | {{ $service->type->service_address }} | @break; diff --git a/resources/views/email/admin/order/reject.blade.php b/resources/views/email/admin/order/reject.blade.php index dbd39b3..d9abee2 100644 --- a/resources/views/email/admin/order/reject.blade.php +++ b/resources/views/email/admin/order/reject.blade.php @@ -9,7 +9,7 @@ | Account | {{ $service->account->name }} | | Service ID | {{ $service->sid }} | | Product | {{ $service->product->name }} | -@switch($service->category) +@switch($service->product->category) @case('broadband') | Address | {{ is_object($service->type) ? $service->type->service_address : 'Not Supplied' }} | @break; diff --git a/resources/views/email/admin/service/cancel.blade.php b/resources/views/email/admin/service/cancel.blade.php index 7c15174..3694085 100644 --- a/resources/views/email/admin/service/cancel.blade.php +++ b/resources/views/email/admin/service/cancel.blade.php @@ -8,7 +8,7 @@ Please cancel the following... | Account | {{ $service->account->name }} | | Service ID | {{ $service->sid }} | | Product | {{ $service->product->name }} | -@switch($service->category) +@switch($service->product->category) @case('broadband') | Address | {{ $service->type->service_address }} | @break; diff --git a/resources/views/email/admin/service/change.blade.php b/resources/views/email/admin/service/change.blade.php index db38dbc..db3aaeb 100644 --- a/resources/views/email/admin/service/change.blade.php +++ b/resources/views/email/admin/service/change.blade.php @@ -8,7 +8,7 @@ Please change the following... | Account | {{ $service->account->name }} | | Service ID | {{ $service->sid }} | | Product | {{ $service->product->name }} | -@switch($service->category) +@switch($service->product->category) @case('broadband') | Address | {{ $service->type->service_address }} | @break; diff --git a/resources/views/theme/backend/adminlte/account/widget/summary_boxes.blade.php b/resources/views/theme/backend/adminlte/account/widget/summary_boxes.blade.php index 60ea198..a8f338f 100644 --- a/resources/views/theme/backend/adminlte/account/widget/summary_boxes.blade.php +++ b/resources/views/theme/backend/adminlte/account/widget/summary_boxes.blade.php @@ -37,7 +37,7 @@
Active Services - {{ Service::active()->whereIn('account_id',$acts)->count() }} /{{ Service::whereIn('account_id',$acts)->count() }} + {{ Service::ServiceActive()->whereIn('account_id',$acts)->count() }} /{{ Service::whereIn('account_id',$acts)->count() }}
diff --git a/resources/views/theme/backend/adminlte/product/report.blade.php b/resources/views/theme/backend/adminlte/product/report.blade.php index 89f637a..c7c5a11 100644 --- a/resources/views/theme/backend/adminlte/product/report.blade.php +++ b/resources/views/theme/backend/adminlte/product/report.blade.php @@ -32,7 +32,7 @@ @foreach (\App\Models\Service::active()->with(['product.translate'])->get()->groupBy('product_id') as $s) {{ $x->id }} - {{ $x->category_name }} + {{ $x->product->category_name }} {{ $x->product->pid }} {{ $x->product->name }} {{ $s->count() }} diff --git a/resources/views/theme/backend/adminlte/service/report.blade.php b/resources/views/theme/backend/adminlte/service/report.blade.php index d4ebe90..6557d0c 100644 --- a/resources/views/theme/backend/adminlte/service/report.blade.php +++ b/resources/views/theme/backend/adminlte/service/report.blade.php @@ -1,3 +1,5 @@ +@use(App\Models\Service) + @extends('adminlte::layouts.app') @section('htmlheader_title') @@ -25,21 +27,20 @@ Product Monthly Cost - Traffic (GB) + Usage Supplier - {{-- @todo This query is expensive still --}} - @foreach (\App\Models\Service::active()->with(['type','product.type.supplied.supplier_detail.supplier','product.translate'])->get() as $o) + @foreach (Service::ServiceActive()->with(['account.taxes','type','product.type.supplied.supplier_detail.supplier','product.translate','type.traffic'])->get() as $o) {{ $o->id }} {{ $o->name }} {{ $o->product->name }} - {{ number_format($o->billing_monthly_price,2) }} + {{ number_format($o->billing_charge_normalised,2) }} {{ number_format($o->product->cost_normalized(),2) }} - {{ $o->category == 'broadband' ? number_format($o->type->usage_summary(0)->sum()/1000,1) : '-' }} + {{ $o->product->hasUsage() ? number_format($o->type->usage_summary(0)->sum()/1000,1) : '-' }} {{ $o->product->supplier->name }} @endforeach @@ -50,51 +51,60 @@ @endsection +@pa(datatables,rowgroup|conditionalpaging|select|searchpanes|searchpanes-left) + @section('page-scripts') - @css(datatables,bootstrap4|fixedheader|responsive|rowgroup|buttons) - @js(datatables,bootstrap4|fixedheader|responsive|rowgroup|buttons) - - - + $(document).ready(function() { + $('#table').DataTable({ + //oSearch: { sSearch: searchString ? decodeURIComponent(searchString) : '' }, + aLengthMenu: [ + [25, 50, 100, 200, -1], + [25, 50, 100, 200, "All"] + ], + paging: true, + pageLength: 25, + conditionalPaging: true, + lengthChange: true, + searching: true, + ordering: true, + info: true, + autoWidth: false, + fixedHeader: true, + order: [ + [2,'asc'], + [1,'asc'], + ], + rowGroup: { + dataSrc: [2], + }, + columnDefs: [ + { + targets: [2], + visible: false, + }, + { + targets: [0,1,3,4,5], + 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>>', + }); + }); + @append \ No newline at end of file diff --git a/resources/views/theme/backend/adminlte/service/widget/information.blade.php b/resources/views/theme/backend/adminlte/service/widget/information.blade.php index 4a1e72a..951e00f 100644 --- a/resources/views/theme/backend/adminlte/service/widget/information.blade.php +++ b/resources/views/theme/backend/adminlte/service/widget/information.blade.php @@ -8,11 +8,11 @@ @endif -
+

Service Information

-
+
@@ -28,7 +28,7 @@ @endif - @if($o->start_at AND $o->isPending()) + @if($o->start_at && $o->isPending()) @@ -47,10 +47,10 @@ @endif - @if($o->active && $o->invoice_to) + @if($o->isActive() && $o->invoiced_to) - + @if($o->paid_to) @@ -59,14 +59,16 @@ @endif @endif + @if($o->status !== 'cancel-pending') - + + @endif diff --git a/resources/views/theme/backend/adminlte/service/widget/internal.blade.php b/resources/views/theme/backend/adminlte/service/widget/internal.blade.php index f3bae91..4a3b35e 100644 --- a/resources/views/theme/backend/adminlte/service/widget/internal.blade.php +++ b/resources/views/theme/backend/adminlte/service/widget/internal.blade.php @@ -1,5 +1,7 @@ - +@use(App\Models\Invoice) @php($c=$o->product) + +
Account{{ $o->order_info_reference ?? '' }}
Pending Connection {{ $o->start_at->format('Y-m-d') }}${{ number_format($o->billing_charge,2) }}
Invoiced To{{ $o->invoice_to->format('Y-m-d') }}{{ $o->invoiced_to->format('Y-m-d') }}
Next Invoice @if($o->suspend_billing)@endif{{ $o->invoice_next->format('Y-m-d') }}@if($o->suspend_billing) SUSPENDED@endif
Next Estimated Invoice${{ number_format($o->next_invoice_items(TRUE)->sum('total'),2) }} *${{ number_format($o->next_invoice_items()->sum('total'),2) }} *
Payment Method @if($o->billing)Direct Debit @else Invoice @endif
@@ -75,16 +77,16 @@ - + @if($p->exists) - - + + @endif diff --git a/resources/views/theme/backend/adminlte/service/widget/invoice.blade.php b/resources/views/theme/backend/adminlte/service/widget/invoice.blade.php index 1383829..f41051b 100644 --- a/resources/views/theme/backend/adminlte/service/widget/invoice.blade.php +++ b/resources/views/theme/backend/adminlte/service/widget/invoice.blade.php @@ -1,6 +1,6 @@
Monthly Price isChargeOverridden()) class="text-danger" @endif> @if($x) - ${{ number_format($b=$o->billing_monthly_price,2) }} + ${{ number_format($b=$o->billing_charge_normalised,2) }} @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 ${{ number_format($a=$o->account->taxed($c->base_cost)*\App\Models\Invoice::billing_change($o->billing_interval,\App\Models\Invoice::BILL_MONTHLY),2) }}${{ number_format($a=$o->account->taxed($c->base_cost)*Invoice::billing_change($o->billing_interval,Invoice::BILL_MONTHLY),2) }} {!! markup($a,$b) !!}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) }}${{ number_format($a=$o->account->taxed($p->base_cost)*\App\Models\Invoice::billing_change($o->billing_interval,\App\Models\Invoice::BILL_MONTHLY),2) }}isChargeOverridden()) class="text-danger" @endif>${{ number_format($b=$o->account->taxed($p->base_charge)*Invoice::billing_change($o->billing_interval,Invoice::BILL_MONTHLY),2) }}${{ number_format($a=$o->account->taxed($p->base_cost)*Invoice::billing_change($o->billing_interval,Invoice::BILL_MONTHLY),2) }} {!! markup($a,$b) !!}
- + @foreach ($x as $io) diff --git a/routes/web.php b/routes/web.php index c052f69..9bf99be 100644 --- a/routes/web.php +++ b/routes/web.php @@ -121,7 +121,7 @@ Route::group(['middleware'=>['auth','role:wholesaler'],'prefix'=>'a'],function() Route::get('report/accounts',[ReportController::class,'accounts']); 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 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']) ->where('o','[0-9]+') ->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::get('invoice/{o}',[InvoiceController::class,'view']) ->where('o','[0-9]+')
{{ $o->name }}${{ number_format(($x=$o->next_invoice_items(TRUE))->sum('total'),2) }}{{ $o->name }}${{ number_format(($x=$o->next_invoice_items())->sum('total'),2) }}