Compare commits

...

4 Commits

Author SHA1 Message Date
d96924bc65 Addresses now a collection
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 33s
Create Docker Image / Final Docker Image Manifest (push) Successful in 9s
2024-07-07 15:18:57 +10:00
b63807fa82 Optimise Service model 2024-07-07 15:18:57 +10:00
b4f3db04fc Removed redundant functions in Account, Optimised User a little more, Moved Ezypay Commands to new Ezypay folder 2024-07-07 15:18:57 +10:00
70e94bf6e6 Fix search and psql like queries need to be ilike for case insensitivity 2024-07-07 10:32:26 +10:00
28 changed files with 288 additions and 383 deletions

View File

@ -32,7 +32,7 @@ class BroadbandTraffic extends Command
public function handle() public function handle()
{ {
if ($this->option('supplier')) { if ($this->option('supplier')) {
$o = Supplier::where('name','like',$this->option('supplier'))->singleOrFail(); $o = Supplier::where('name','ilike',$this->option('supplier'))->singleOrFail();
Job::dispatchSync($o->id); Job::dispatchSync($o->id);
return; return;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Console\Commands; namespace App\Console\Commands\Ezypay;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@ -8,14 +8,14 @@ use Illuminate\Console\Command;
use App\Classes\External\Payments\Ezypay; use App\Classes\External\Payments\Ezypay;
use App\Models\Account; use App\Models\Account;
class PaymentsEzypayNext extends Command class PaymentNext extends Command
{ {
/** /**
* The name and signature of the console command. * The name and signature of the console command.
* *
* @var string * @var string
*/ */
protected $signature = 'payments:ezypay:next'; protected $signature = 'ezypay:payment:next';
/** /**
* The console command description. * The console command description.
@ -35,14 +35,12 @@ class PaymentsEzypayNext extends Command
foreach ($poo->getCustomers() as $c) { foreach ($poo->getCustomers() as $c) {
if ($c->BillingStatus == 'Inactive') { if ($c->BillingStatus == 'Inactive') {
$this->info(sprintf('Ignoring INACTIVE: [%s] %s %s',$c->EzypayReferenceNumber,$c->Firstname,$c->Surname)); $this->comment(sprintf('Ignoring INACTIVE: [%s] %s %s',$c->EzypayReferenceNumber,$c->Firstname,$c->Surname));
continue; continue;
} }
// Load Account Details from ReferenceId // Load Account Details from ReferenceId
$ao = Account::where('site_id',(int)substr($c->ReferenceId,0,2)) $ao = Account::find((int)substr($c->ReferenceId,2,4));
->where('id',(int)substr($c->ReferenceId,2,4))
->single();
if (! $ao) { if (! $ao) {
$this->warn(sprintf('Missing: [%s] %s %s (%s)',$c->EzypayReferenceNumber,$c->Firstname,$c->Surname,$c->ReferenceId)); $this->warn(sprintf('Missing: [%s] %s %s (%s)',$c->EzypayReferenceNumber,$c->Firstname,$c->Surname,$c->ReferenceId));
@ -50,7 +48,14 @@ class PaymentsEzypayNext extends Command
} }
// Get Due Invoices // Get Due Invoices
$account_due = $ao->dueInvoices()->sum('due'); $invoice_due = $ao->invoiceSummaryDue()->get();
$this->info(sprintf('Account [%s] (%s) has [%d] invoices due, totalling [%3.2f]',
$ao->lid,
$ao->name,
$invoice_due->count(),
($due=$invoice_due->sum('_balance')),
));
$next_pay = $poo->getDebits([ $next_pay = $poo->getDebits([
'customerId'=>$c->Id, 'customerId'=>$c->Id,
@ -59,32 +64,32 @@ class PaymentsEzypayNext extends Command
])->reverse()->first(); ])->reverse()->first();
if ($next_pay->Status !== 'Pending') { if ($next_pay->Status !== 'Pending') {
$this->warn(sprintf('Next payment is not pending for (%s)',$ao->name)); $this->warn(sprintf('- Next payment is not pending for (%s)',$ao->name));
continue; continue;
} }
$next_paydate = Carbon::createFromTimeString($next_pay->Date); $next_paydate = Carbon::createFromTimeString($next_pay->Date);
if ($next_pay->Amount < $account_due) if ($next_pay->Amount < $due)
$this->warn(sprintf('Next payment on [%s] for (%s) [%s] not sufficient for outstanding balance [%s]', $this->error(sprintf('- Next payment on [%s] for (%s) [%s] not sufficient for outstanding balance [%s]',
$next_paydate->format('Y-m-d'), $next_paydate->format('Y-m-d'),
$ao->name, $ao->name,
number_format($next_pay->Amount,2), number_format($next_pay->Amount,2),
number_format($account_due,2))); number_format($due,2)));
elseif ($next_pay->Amount > $account_due) elseif ($next_pay->Amount > $due)
$this->warn(sprintf('Next payment on [%s] for (%s) [%s] is too much for outstanding balance [%s]', $this->warn(sprintf('- Next payment on [%s] for (%s) [%s] is too much for outstanding balance [%s]',
$next_paydate->format('Y-m-d'), $next_paydate->format('Y-m-d'),
$ao->name, $ao->name,
number_format($next_pay->Amount,2), number_format($next_pay->Amount,2),
number_format($account_due,2))); number_format($due,2)));
else else
$this->info(sprintf('Next payment on [%s] for (%s) [%s] will cover outstanding balance [%s]', $this->info(sprintf('- Next payment on [%s] for (%s) [%s] will cover outstanding balance [%s]',
$next_paydate->format('Y-m-d'), $next_paydate->format('Y-m-d'),
$ao->name, $ao->name,
number_format($next_pay->Amount,2), number_format($next_pay->Amount,2),
number_format($account_due,2))); number_format($due,2)));
} }
} }
} }

View File

@ -1,20 +1,20 @@
<?php <?php
namespace App\Console\Commands; namespace App\Console\Commands\Ezypay;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Classes\External\Payments\Ezypay; use App\Classes\External\Payments\Ezypay;
use App\Jobs\PaymentsImport as Job; use App\Jobs\PaymentsImport as Job;
class PaymentsEzypayImport extends Command class PaymentsImport extends Command
{ {
/** /**
* The name and signature of the console command. * The name and signature of the console command.
* *
* @var string * @var string
*/ */
protected $signature = 'payments:ezypay:import'; protected $signature = 'ezypay:payment:import';
/** /**
* The console command description. * The console command description.

View File

@ -7,7 +7,7 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Gate;
use App\Models\{Account,Invoice,Payment,Service,Supplier,User}; use App\Models\{Account,Invoice,Payment,Service,User};
class SearchController extends Controller class SearchController extends Controller
{ {
@ -29,10 +29,10 @@ class SearchController extends Controller
if (! $request->input('term')) if (! $request->input('term'))
return $result; return $result;
$account_ids = ($x=Auth::user()->accounts)->pluck('id'); $account_ids = ($x=Auth::user()->accounts_all)->pluck('id');
$user_ids = $x->transform(function($item) { return $item->user;})->pluck('id'); $user_ids = $x->pluck('user_id')->unique();
# Look for User // Look for User
foreach (User::Search($request->input('term')) foreach (User::Search($request->input('term'))
->whereIN('id',$user_ids) ->whereIN('id',$user_ids)
->orderBy('lastname') ->orderBy('lastname')
@ -42,7 +42,7 @@ class SearchController extends Controller
$result->push(['name'=>sprintf('%s (%s) - %s',$o->name,$o->lid,$o->email),'value'=>'/u/home/'.$o->id,'category'=>'Users']); $result->push(['name'=>sprintf('%s (%s) - %s',$o->name,$o->lid,$o->email),'value'=>'/u/home/'.$o->id,'category'=>'Users']);
} }
# Look for User by Supplier // Look for User by their Supplier ID with some suppliers
if (is_numeric($request->input('term'))) if (is_numeric($request->input('term')))
foreach (User::select(['users.*','suppliers.name AS supplier_name','supplier_user.id AS pivot_id']) foreach (User::select(['users.*','suppliers.name AS supplier_name','supplier_user.id AS pivot_id'])
->join('supplier_user',['supplier_user.user_id'=>'users.id']) ->join('supplier_user',['supplier_user.user_id'=>'users.id'])
@ -56,16 +56,16 @@ class SearchController extends Controller
$result->push(['name'=>sprintf('%s (%s:%s)',$o->name,$o->supplier_name,$o->pivot_id),'value'=>'/u/home/'.$o->id,'category'=>'Suppliers']); $result->push(['name'=>sprintf('%s (%s:%s)',$o->name,$o->supplier_name,$o->pivot_id),'value'=>'/u/home/'.$o->id,'category'=>'Suppliers']);
} }
# Look for Account // Look for Account
foreach (Account::Search($request->input('term')) foreach (Account::Search($request->input('term'))
->whereIN('user_id',$user_ids) ->whereIN('id',$account_ids)
->orderBy('company') ->orderBy('company')
->limit(10)->get() as $o) ->limit(10)->get() as $o)
{ {
$result->push(['name'=>sprintf('%s (%s)',$o->company,$o->lid),'value'=>'/u/home/'.$o->user_id,'category'=>'Accounts']); $result->push(['name'=>sprintf('%s (%s)',$o->name,$o->lid),'value'=>'/u/home/'.$o->user_id,'category'=>'Accounts']);
} }
# Look for a Service // Look for a Service
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')
@ -74,7 +74,7 @@ class SearchController extends Controller
$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->category_name]);
} }
# Look for an Invoice // Look for an Invoice
foreach (Invoice::Search($request->input('term')) foreach (Invoice::Search($request->input('term'))
->whereIN('account_id',$account_ids) ->whereIN('account_id',$account_ids)
->orderBy('id') ->orderBy('id')
@ -84,7 +84,7 @@ class SearchController extends Controller
} }
if (Gate::any(['wholesaler'],new Payment)) { if (Gate::any(['wholesaler'],new Payment)) {
# Look for Payments // Look for Payments
foreach (Payment::Search($request->input('term')) foreach (Payment::Search($request->input('term'))
->whereIN('account_id',$account_ids) ->whereIN('account_id',$account_ids)
->limit(10)->get() as $o) ->limit(10)->get() as $o)

View File

@ -27,6 +27,12 @@ class Account extends Model implements IDs
/* STATIC */ /* STATIC */
/**
* A list of invoices that are in credit for all accounts
*
* @param Collection|NULL $invoices
* @return Collection
*/
public static function InvoicesCredit(Collection $invoices=NULL): Collection public static function InvoicesCredit(Collection $invoices=NULL): Collection
{ {
return (new self) return (new self)
@ -34,6 +40,12 @@ class Account extends Model implements IDs
->get(); ->get();
} }
/**
* A list of invoices that are due for all accounts
*
* @param Collection|NULL $invoices
* @return Collection
*/
public static function InvoicesDue(Collection $invoices=NULL): Collection public static function InvoicesDue(Collection $invoices=NULL): Collection
{ {
return (new self) return (new self)
@ -73,11 +85,12 @@ class Account extends Model implements IDs
return $this->belongsTo(Country::class); return $this->belongsTo(Country::class);
} }
public function external() /**
{ * Group this account is assigned to
return $this->belongsToMany(External\Integrations::class,'external_account',NULL,'external_integration_id'); * Groups are used for pricing control
} *
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
*/
public function group() public function group()
{ {
return $this->hasOneThrough(Group::class,AccountGroup::class,'account_id','id','id','group_id'); return $this->hasOneThrough(Group::class,AccountGroup::class,'account_id','id','id','group_id');
@ -85,8 +98,6 @@ class Account extends Model implements IDs
/** /**
* Invoices created for this account * Invoices created for this account
*
* @todo This needs to be optimised, to only return outstanding invoices and invoices for a specific age (eg: 2 years worth)
*/ */
public function invoices() public function invoices()
{ {
@ -96,13 +107,11 @@ class Account extends Model implements IDs
/** /**
* Relation to only return active invoices * Relation to only return active invoices
*
* @todo Only return active invoice_items
*/ */
public function invoices_active() public function invoices_active()
{ {
return $this->invoices() return $this->invoices()
->active(); ->with('active',TRUE);;
} }
/** /**
@ -116,19 +125,19 @@ class Account extends Model implements IDs
/** /**
* Relation to only return active payments * Relation to only return active payments
*
* @todo Only return active payment_items
*/ */
public function payments_active() public function payments_active()
{ {
return $this->payments() return $this->payments()
->active(); ->with('active',TRUE);
} }
/**
* Return the link to a provider's info for this account
*/
public function providers() public function providers()
{ {
return $this->belongsToMany(ProviderOauth::class,'account__provider') return $this->belongsToMany(ProviderOauth::class,'account__provider')
->where('account__provider.site_id',$this->site_id)
->withPivot('ref','synctoken','created_at','updated_at'); ->withPivot('ref','synctoken','created_at','updated_at');
} }
@ -150,6 +159,9 @@ class Account extends Model implements IDs
->active(); ->active();
} }
/**
* Taxes applicable for this account
*/
public function taxes() public function taxes()
{ {
return $this->hasMany(Tax::class,'country_id','country_id') return $this->hasMany(Tax::class,'country_id','country_id')
@ -180,10 +192,10 @@ class Account extends Model implements IDs
$query->where('id','like','%'.$term.'%'); $query->where('id','like','%'.$term.'%');
} else { } else {
$query->where('company','like','%'.$term.'%') $query->where('company','ilike','%'.$term.'%')
->orWhere('address1','like','%'.$term.'%') ->orWhere('address1','ilike','%'.$term.'%')
->orWhere('address2','like','%'.$term.'%') ->orWhere('address2','ilike','%'.$term.'%')
->orWhere('city','like','%'.$term.'%'); ->orWhere('city','ilike','%'.$term.'%');
} }
return $query; return $query;
@ -194,10 +206,9 @@ class Account extends Model implements IDs
/** /**
* Get the address for the account * Get the address for the account
* *
* @return array * @return Collection
* @todo Change this to return a collection
*/ */
public function getAddressAttribute(): array public function getAddressAttribute(): Collection
{ {
return collect([ return collect([
'address1' => $this->address1, 'address1' => $this->address1,
@ -207,9 +218,7 @@ class Account extends Model implements IDs
$this->state, $this->state,
$this->zip) $this->zip)
]) ])
->filter() ->filter();
->values()
->toArray();
} }
/** /**
@ -219,7 +228,7 @@ class Account extends Model implements IDs
*/ */
public function getNameAttribute(): string public function getNameAttribute(): string
{ {
return $this->company ?: ($this->user_id ? $this->user->getSurFirstNameAttribute() : 'LID:'.$this->id); return $this->company ?: ($this->user_id ? $this->user->getNameSurFirstAttribute() : 'LID:'.$this->id);
} }
/** /**
@ -234,19 +243,6 @@ class Account extends Model implements IDs
/* METHODS */ /* METHODS */
/**
* Get the due invoices on an account
*
* @return mixed
* @deprecated use invoiceSummary->filter(_balance > 0)
*/
public function dueInvoices()
{
return $this->invoices->filter(function($item) {
return $item->active AND $item->due > 0;
});
}
/** /**
* List of invoices (summary) for this account * List of invoices (summary) for this account
* *

View File

@ -11,8 +11,8 @@ class Cost extends Model
{ {
use HasFactory; use HasFactory;
protected $dates = [ protected $casts = [
'billed_at', 'billed_at' => 'datetime:Y-m-d',
]; ];
protected $with = [ protected $with = [

View File

@ -1,27 +0,0 @@
<?php
namespace App\Models\External;
use Illuminate\Database\Eloquent\Model;
use App\Models\User;
class Integrations extends Model
{
public $table = 'external_integrations';
public function user()
{
return $this->belongsTo(User::class);
}
function scopeActive()
{
return $this->where('active',TRUE);
}
function scopeType($query,string $type)
{
return $query->where('type',$type);
}
}

View File

@ -38,7 +38,7 @@ class Invoice extends Model implements IDs
protected $casts = [ protected $casts = [
'created_at' => 'datetime:Y-m-d', 'created_at' => 'datetime:Y-m-d',
'due_at' => 'datetime:Y-m-d', 'due_at' => 'datetime:Y-m-d',
'reminders'=>'json', 'reminders' => 'json',
'_paid_at' => 'datetime:Y-m-d', '_paid_at' => 'datetime:Y-m-d',
]; ];

View File

@ -2,20 +2,17 @@
namespace App\Models; namespace App\Models;
use Carbon\Carbon;
use Exception; use Exception;
use Illuminate\Database\Eloquent\Casts\AsCollection; use Illuminate\Database\Eloquent\Casts\AsCollection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
use Leenooks\Carbon as LeenooksCarbon; use Leenooks\Carbon;
use Leenooks\Casts\LeenooksCarbon;
use App\Models\Product\Type; use App\Models\Product\Type;
use App\Interfaces\IDs; use App\Interfaces\IDs;
@ -48,7 +45,7 @@ use App\Traits\ScopeServiceUserAuthorised;
* + supplied : The model of the supplier's product used for this service. * + supplied : The model of the supplier's product used for this service.
* *
* Methods: * Methods:
* + isChargeOverriden : Has the price been overridden? * + isChargeOverridden : Has the price been overridden?
* + isPending : Is this a pending active service * + isPending : Is this a pending active service
* *
* @package App\Models * @package App\Models
@ -61,33 +58,9 @@ class Service extends Model implements IDs
protected $casts = [ protected $casts = [
'order_info' => AsCollection::class, 'order_info' => AsCollection::class,
'invoice_last_at' => 'datetime:Y-m-d', // @todo Can these be removed, since we can work out invoice dynamically now 'invoice_next_at' => LeenooksCarbon::class, // @todo Can this be removed, since start_at provides this functionality
'invoice_next_at' => 'datetime:Y-m-d', // @todo Can these be removed, since we can work out invoice dynamically now 'stop_at' => LeenooksCarbon::class,
'stop_at' => 'datetime:Y-m-d', 'start_at' => LeenooksCarbon::class,
'start_at' => 'datetime:Y-m-d',
];
/** @deprecated */
protected $appends = [
'category_name',
'name_short',
];
/** @deprecated */
protected $visible = [
// 'account_name',
// 'admin_service_id_url',
'active',
'category_name',
// 'billing_price',
// 'data_orig',
'id',
'name_short',
// 'next_invoice',
// 'product.name',
// 'service_id',
// 'service_id_url',
// 'status',
]; ];
protected $with = [ protected $with = [
@ -295,6 +268,26 @@ class Service extends Model implements IDs
], ],
]; ];
/* STATIC */
/**
* List of services that are being changed
*
* Services are being changed, when they are active, but their order status is not active
*
* @param User $uo
* @return Collection
*/
public static function movements(User $uo): Collection
{
return (new self)
->active()
->serviceUserAuthorised($uo)
->where('order_status','!=','ACTIVE')
->with(['account','product'])
->get();
}
/* INTERFACES */ /* INTERFACES */
/** /**
@ -317,24 +310,10 @@ class Service extends Model implements IDs
return sprintf('%02s-%04s.%s',$this->site_id,$this->account_id,$this->getLIDattribute()); return sprintf('%02s-%04s.%s',$this->site_id,$this->account_id,$this->getLIDattribute());
} }
/* STATIC */
public static function movements(User $uo): Collection
{
return (new self)
->active()
->serviceUserAuthorised($uo)
->where('order_status','!=','ACTIVE')
->with(['account','product'])
->get();
}
/* RELATIONS */ /* RELATIONS */
/** /**
* Account the service belongs to * Account the service belongs to
*
* @return BelongsTo
*/ */
public function account() public function account()
{ {
@ -343,8 +322,6 @@ class Service extends Model implements IDs
/** /**
* Return automatic billing details * Return automatic billing details
*
* @return HasOne
*/ */
public function billing() public function billing()
{ {
@ -353,8 +330,6 @@ class Service extends Model implements IDs
/** /**
* Return Charges associated with this Service * Return Charges associated with this Service
*
* @return HasMany
*/ */
public function charges() public function charges()
{ {
@ -424,8 +399,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)
->orderBy('service_id') ->orderBy('start_at','desc');
->orderBy('start_at');
} }
/** /**
@ -444,8 +418,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)
->orderBy('service_id') ->orderBy('start_at','desc');
->orderBy('start_at');
} }
/** /**
@ -457,10 +430,14 @@ class Service extends Model implements IDs
->where('active',TRUE); ->where('active',TRUE);
} }
public function invoiced_service_items_active_recent()
{
return $this->invoiced_service_items_active()
->limit(10);
}
/** /**
* User that ordered the service * User that ordered the service
*
* @return BelongsTo
*/ */
public function orderedby() public function orderedby()
{ {
@ -469,8 +446,6 @@ class Service extends Model implements IDs
/** /**
* Product of the service * Product of the service
*
* @return BelongsTo
*/ */
public function product() public function product()
{ {
@ -479,8 +454,6 @@ class Service extends Model implements IDs
/** /**
* Return a child model with details of the service * Return a child model with details of the service
*
* @return MorphTo
*/ */
public function type() public function type()
{ {
@ -530,34 +503,50 @@ class Service extends Model implements IDs
return $query->select('services.*') return $query->select('services.*')
->where('services.id','like',$t) ->where('services.id','like',$t)
->leftJoin('service_broadband',['service_broadband.service_id'=>'services.id']) ->leftJoin('service_broadband',['service_broadband.service_id'=>'services.id'])
->orWhere('service_broadband.service_number','like',$t) ->orWhere('service_broadband.service_number','ilike',$t)
->orWhere('service_broadband.service_address','like',$t) ->orWhere('service_broadband.service_address','ilike',$t)
->orWhere('service_broadband.ipaddress','like',$t) ->orWhere('service_broadband.ipaddress','ilike',$t)
->leftJoin('service_phone',['service_phone.service_id'=>'services.id']) ->leftJoin('service_phone',['service_phone.service_id'=>'services.id'])
->orWhere('service_phone.service_number','like',$t) ->orWhere('service_phone.service_number','ilike',$t)
->orWhere('service_phone.service_address','like',$t) ->orWhere('service_phone.service_address','ilike',$t)
->leftJoin('service_domain',['service_domain.service_id'=>'services.id']) ->leftJoin('service_domain',['service_domain.service_id'=>'services.id'])
->orWhere('service_domain.domain_name','like',$t) ->orWhere('service_domain.domain_name','ilike',$t)
->leftJoin('service_email',['service_email.service_id'=>'services.id']) ->leftJoin('service_email',['service_email.service_id'=>'services.id'])
->orWhere('service_email.domain_name','like',$t) ->orWhere('service_email.domain_name','ilike',$t)
->leftJoin('service_host',['service_host.service_id'=>'services.id']) ->leftJoin('service_host',['service_host.service_id'=>'services.id'])
->orWhere('service_host.domain_name','like',$t); ->orWhere('service_host.domain_name','ilike',$t);
} }
/* ATTRIBUTES */ /* ATTRIBUTES */
/** /**
* How much do we charge for this service, base on the current recur schedule * How much do we charge for this service, base on the current recur schedule
* price in the DB overrides the base price used
* *
* @return float * @return float
*/ */
public function getBillingChargeAttribute(): float public function getBillingChargeAttribute(): float
{ {
// @todo Temporary for services that dont have recur_schedule set. // If recur_schedule is null, then we only bill this item once
if (is_null($this->recur_schedule) OR is_null($this->product->getBaseChargeAttribute($this->recur_schedule,$this->account->group))) if (is_null($this->recur_schedule) && $this->getInvoiceToAttribute())
$this->price=0; $this->price = 0;
return $this->addTax(is_null($this->price) ? $this->product->getBaseChargeAttribute($this->recur_schedule,$this->account->group) : $this->price); return $this->account->taxed(
is_null($this->price)
? $this->product->getBaseChargeAttribute($this->recur_schedule,$this->account->group)
: $this->price
);
}
/**
* Determine a monthly price for a service, even if it is billed at a different frequency
*
* @return float
* @throws Exception
*/
public function getBillingChargeNormalisedAttribute(): float
{
return number_format($this->getBillingChargeAttribute()*Invoice::billing_change($this->recur_schedule,$this->offering->billing_interval),2);
} }
/** /**
@ -585,18 +574,29 @@ class Service extends Model implements IDs
* *
* @return float * @return float
* @throws Exception * @throws Exception
* @deprecated use class::charge_normalized() * @deprecated use class::billing_charge_normalised()
*/ */
public function getBillingMonthlyPriceAttribute(): float public function getBillingMonthlyPriceAttribute(): float
{ {
return number_format($this->getBillingChargeAttribute()/Arr::get(Invoice::billing_periods,$this->recur_schedule.'.interval',1),2); Log::alert('SMO:! Deprecated function getBillingMonthlyPriceAttribute()');
return $this->getBillingChargeNormalisedAttribute();
} }
/**
* Service Category ID
*
* @return string
*/
public function getCategoryAttribute(): string public function getCategoryAttribute(): string
{ {
return $this->product->category; return $this->product->category;
} }
/**
* Service Category Name
*
* @return string
*/
public function getCategoryNameAttribute(): string public function getCategoryNameAttribute(): string
{ {
return $this->product->category_name; return $this->product->category_name;
@ -607,7 +607,7 @@ class Service extends Model implements IDs
* *
* Service contracts end the later of the start_date + contract_term or the expire date. * Service contracts end the later of the start_date + contract_term or the expire date.
* *
* @return Carbon * @return Carbon|null
*/ */
public function getContractEndAttribute(): ?Carbon public function getContractEndAttribute(): ?Carbon
{ {
@ -640,49 +640,32 @@ class Service extends Model implements IDs
return max($this->supplied->contract_term,$this->product->type->contract_term); return max($this->supplied->contract_term,$this->product->type->contract_term);
} }
/**
* Date the service expires, also represents when it is paid up to
*
* @return string
* @todo
*/
public function getExpiresAttribute(): string
{
abort(500,'Not implemented');
return 'TBA';
}
/** /**
* Return the date for the next invoice * Return the date for the next invoice
* *
* @return LeenooksCarbon * In priority order, the next is either
* + The next day after it was last invoiced to (stop_at)
* + The earlier of invoice_next_at and start_at dates
* + Today
*
* @return Carbon
*/ */
public function getInvoiceNextAttribute(): LeenooksCarbon public function getInvoiceNextAttribute(): Carbon
{ {
$last = $this->getInvoiceToAttribute(); $last = $this->getInvoiceToAttribute();
return $last return $last
? $last->addDay() ? $last->addDay()
: ($this->invoice_next_at ? $this->invoice_next_at->clone() : ($this->start_at ?: LeenooksCarbon::now())); : (min($this->start_at,$this->invoice_next_at) ?: Carbon::now());
}
/**
* We need to cast some dates to LeenooksCarbon to get access to startOfHalf()/endOfHalf() methods
*
* @param $value
* @return LeenooksCarbon|null
*/
public function getInvoiceNextAtAttribute($value): ?LeenooksCarbon
{
return $value ? LeenooksCarbon::create($value) : NULL;
} }
/** /**
* Return the end date for the next invoice * Return the end date for the next invoice
* *
* @return mixed * @return Carbon
* @throws Exception * @throws Exception
*/ */
public function getInvoiceNextEndAttribute() public function getInvoiceNextEndAttribute(): Carbon
{ {
switch ($this->recur_schedule) { switch ($this->recur_schedule) {
case Invoice::BILL_WEEKLY: case Invoice::BILL_WEEKLY:
@ -706,7 +689,7 @@ class Service extends Model implements IDs
case Invoice::BILL_SEMI_YEARLY: case Invoice::BILL_SEMI_YEARLY:
$date = $this->product->price_recur_strict $date = $this->product->price_recur_strict
? $this->getInvoiceNextAttribute()->endOfHalf() ? $this->getInvoiceNextAttribute()->endOfHalf()
: $this->getInvoiceNextAttribute()->addQuarter(2)->subDay(); : $this->getInvoiceNextAttribute()->addQuarters(2)->subDay();
break; break;
case Invoice::BILL_YEARLY: case Invoice::BILL_YEARLY:
@ -717,11 +700,12 @@ class Service extends Model implements IDs
case Invoice::BILL_TWOYEARS: case Invoice::BILL_TWOYEARS:
if (! $this->product->price_recur_strict) { if (! $this->product->price_recur_strict) {
$date = $this->getInvoiceNextAttribute()->addYear(2)->subDay(); $date = $this->getInvoiceNextAttribute()->addYears(2)->subDay();
} else { } else {
$date = $this->getInvoiceNextAttribute()->addYear(2)->subDay()->endOfYear(); $date = $this->getInvoiceNextAttribute()->addYears(2)->subDay()->endOfYear();
// Make sure we end on an even year
if ($date->clone()->addDay()->year%2) if ($date->clone()->addDay()->year%2)
$date = $date->subYear(); $date = $date->subYear();
} }
@ -729,25 +713,25 @@ class Service extends Model implements IDs
// NOTE: price_recur_strict ignored // NOTE: price_recur_strict ignored
case Invoice::BILL_THREEYEARS: case Invoice::BILL_THREEYEARS:
$date = $this->getInvoiceNextAttribute()->addYear(3)->subDay(); $date = $this->getInvoiceNextAttribute()->addYears(3)->subDay();
break; break;
// NOTE: price_recur_strict ignored // NOTE: price_recur_strict ignored
case Invoice::BILL_FOURYEARS: case Invoice::BILL_FOURYEARS:
$date = $this->getInvoiceNextAttribute()->addYear(4)->subDay(); $date = $this->getInvoiceNextAttribute()->addYears(4)->subDay();
break; break;
// NOTE: price_recur_strict ignored // NOTE: price_recur_strict ignored
case Invoice::BILL_FIVEYEARS: case Invoice::BILL_FIVEYEARS:
$date = $this->getInvoiceNextAttribute()->addYear(5)->subDay(); $date = $this->getInvoiceNextAttribute()->addYears(5)->subDay();
break; break;
default: default:
throw new Exception('Unknown recur_schedule'); throw new Exception('Unknown recur_schedule');
} }
// If the invoice has an end date, our invoice period shouldnt be greater than that. // If the invoice has an end date, our invoice period shouldnt be greater than that (might be terminating).
if ($this->stop_at AND $this->stop_at < $date) if ($this->stop_at && ($this->stop_at < $date))
$date = $this->stop_at; $date = $this->stop_at;
return $date; return $date;
@ -765,43 +749,43 @@ class Service extends Model implements IDs
if (! $this->product->price_recur_strict) if (! $this->product->price_recur_strict)
return 1; return 1;
$n = round($this->invoice_next->diffInDays($this->invoice_next_end),0); $n = $this->invoice_next->diffInDays($this->invoice_next_end);
switch ($this->recur_schedule) { switch ($this->recur_schedule) {
case Invoice::BILL_WEEKLY: case Invoice::BILL_WEEKLY:
$d = $this->invoice_next->addWeek()->startOfWeek()->diff($this->invoice_next_end->startOfWeek())->days; $d = $this->invoice_next_end->addWeek()->startOfWeek()->diffInDays($this->invoice_next->startOfWeek());
break; break;
case Invoice::BILL_MONTHLY: case Invoice::BILL_MONTHLY:
$d = $this->invoice_next->addMonth()->startOfMonth()->diff($this->invoice_next_end->startOfMonth())->days; $d = $this->invoice_next_end->addMonth()->startOfMonth()->diffInDays($this->invoice_next->startOfMonth());
break; break;
case Invoice::BILL_QUARTERLY: case Invoice::BILL_QUARTERLY:
$d = round($this->invoice_next_end->startOfQuarter()->diffInDays($this->invoice_next->addQuarter()->startOfQuarter()),1); $d = $this->invoice_next_end->startOfQuarter()->diffInDays($this->invoice_next->addQuarter()->startOfQuarter());
break; break;
case Invoice::BILL_SEMI_YEARLY: case Invoice::BILL_SEMI_YEARLY:
$d = $this->invoice_next->addQuarter(2)->startOfHalf()->diff($this->invoice_next_end->startOfHalf())->days; $d = $this->invoice_next_end->addQuarter(2)->startOfHalf()->diffInDays($this->invoice_next->startOfHalf());
break; break;
case Invoice::BILL_YEARLY: case Invoice::BILL_YEARLY:
$d = $this->invoice_next->addYear()->startOfYear()->diff($this->invoice_next_end->startOfYear())->days; $d = $this->invoice_next_end->addYear()->startOfYear()->diffInDays($this->invoice_next->startOfYear());
break; break;
case Invoice::BILL_TWOYEARS: case Invoice::BILL_TWOYEARS:
$d = $this->invoice_next->addYear(2)->startOfYear()->diff($this->invoice_next_end->subyear(2))->days-1; $d = $this->invoice_next_end->addYear(2)->startOfYear()->diffInDays($this->invoice_next->subyear(2))-1;
break; break;
case Invoice::BILL_THREEYEARS: case Invoice::BILL_THREEYEARS:
$d = $this->invoice_next->addYear(3)->startOfYear()->diff($this->invoice_next_end->subyear(3))->days-1; $d = $this->invoice_next_end->addYear(3)->startOfYear()->diffInDays($this->invoice_next->subyear(3))-1;
break; break;
case Invoice::BILL_FOURYEARS: case Invoice::BILL_FOURYEARS:
$d = $this->invoice_next->addYear(3)->startOfYear()->diff($this->invoice_next_end->subyear(4))->days-1; $d = $this->invoice_next_end->addYear(3)->startOfYear()->diffInDays($this->invoice_next->subyear(4))-1;
break; break;
case Invoice::BILL_FIVEYEARS: case Invoice::BILL_FIVEYEARS:
$d = $this->invoice_next->addYear(3)->startOfYear()->diff($this->invoice_next_end->subyear(5))->days-1; $d = $this->invoice_next_end->addYear(3)->startOfYear()->diffInDays($this->invoice_next->subyear(5))-1;
break; break;
default: default:
@ -814,12 +798,12 @@ class Service extends Model implements IDs
/** /**
* Get the date that the service has been invoiced to * Get the date that the service has been invoiced to
* *
* @return LeenooksCarbon|null * @return Carbon|null
*/ */
public function getInvoiceToAttribute(): ?LeenooksCarbon public function getInvoiceToAttribute(): ?Carbon
{ {
return ($x=$this->invoice_items->filter(function($item) { return $item->item_type === 0;}))->count() return ($x=$this->invoiced_service_items_active_recent)->count()
? $x->last()->stop_at ? $x->first()->stop_at
: NULL; : NULL;
} }
@ -863,7 +847,7 @@ class Service extends Model implements IDs
/** /**
* The product we supply for this service * The product we supply for this service
* *
* @return Model * @return Type
*/ */
public function getOfferingAttribute(): Type public function getOfferingAttribute(): Type
{ {
@ -872,12 +856,12 @@ class Service extends Model implements IDs
public function getOrderInfoNotesAttribute(): ?string public function getOrderInfoNotesAttribute(): ?string
{ {
return $this->getOrderInfoValue('notes'); return $this->orderInfo('notes');
} }
public function getOrderInfoReferenceAttribute(): ?string public function getOrderInfoReferenceAttribute(): ?string
{ {
return $this->getOrderInfoValue('reference'); return $this->orderInfo('reference');
} }
/** /**
@ -904,23 +888,12 @@ class Service extends Model implements IDs
* @param $value * @param $value
* @return int * @return int
*/ */
public function getRecurScheduleAttribute($value): int public function xgetRecurScheduleAttribute($value): int
{ {
// If recur_schedule not set, default to quarterly // If recur_schedule not set, default to quarterly
return $value ?? Invoice::BILL_QUARTERLY; return $value ?? Invoice::BILL_QUARTERLY;
} }
/**
* We need to cast some dates to LeenooksCarbon to get access to startOfHalf()/endOfHalf() methods
*
* @param $value
* @return LeenooksCarbon
*/
public function getStartAtAttribute($value): LeenooksCarbon
{
return LeenooksCarbon::create($value);
}
/** /**
* Return the Service Status * Return the Service Status
* *
@ -928,39 +901,7 @@ class Service extends Model implements IDs
*/ */
public function getStatusAttribute(): string public function getStatusAttribute(): string
{ {
if (! $this->order_status) return $this->active ? $this->order_status : 'INACTIVE';
return $this->active ? 'ACTIVE' : 'INACTIVE';
else
return $this->order_status;
}
/**
* Return an HTML status box
*
* @return string
* @todo Add in the other status'
*/
public function getStatusHTMLAttribute(): string
{
$class = NULL;
if ($this->isPending())
$class = 'badge-warning';
else
switch ($this->status)
{
case 'ACTIVE':
$class = 'badge-success';
break;
case 'INACTIVE':
$class = 'badge-danger';
break;
}
return $class
? sprintf('<span class="badge %s">%s</span>',$class,$this->status)
: $this->status;
} }
/** /**
@ -1107,34 +1048,6 @@ class Service extends Model implements IDs
: collect(); : collect();
} }
/**
* Add applicable tax to the cost
*
* @todo This needs to be calculated, not fixed at 1.1
* @todo move all tax calculations into product
* @param float $value
* @return float
*/
private function addTax(float $value): float
{
return round($value*1.1,2);
}
/**
* Return a normalize price dependent on the product, ie: Broadband = Monthly, Domain = Yearly, etc
*
* @return float
*/
public function charge_normalized(): float
{
return number_format($this->getBillingChargeAttribute()*Invoice::billing_change($this->recur_schedule,$this->offering->billing_interval),2);
}
private function getOrderInfoValue(string $key): ?string
{
return $this->order_info ? $this->order_info->get($key) : NULL;
}
/** /**
* Get the stage parameters * Get the stage parameters
* *
@ -1176,16 +1089,6 @@ class Service extends Model implements IDs
return collect($result); return collect($result);
} }
/**
* Does this service have traffic data to be graphed
*
* @return bool
*/
public function hasUsage(): bool
{
return $this->product->hasUsage();
}
/** /**
* Return this service invoices * Return this service invoices
*/ */
@ -1200,11 +1103,10 @@ class Service extends Model implements IDs
* Determine if a service is active. It is active, if active=1, or the order_status is not in self::INACTIVE_STATUS[] * Determine if a service is active. It is active, if active=1, or the order_status is not in self::INACTIVE_STATUS[]
* *
* @return bool * @return bool
* @todo Remove active and have order_status reflect whether active or not
*/ */
public function isActive(): bool public function isActive(): bool
{ {
return $this->active OR ($this->order_status AND ! in_array($this->order_status,self::INACTIVE_STATUS)); return $this->attributes['active'] || ($this->order_status && (! in_array($this->order_status,self::INACTIVE_STATUS)));
} }
/** /**
@ -1214,7 +1116,7 @@ class Service extends Model implements IDs
* @return bool * @return bool
* @todo Can we use the gates to achieve this? * @todo Can we use the gates to achieve this?
*/ */
public function isAuthorised(string $role): bool private function isAuthorised(string $role): bool
{ {
switch(Auth::user()->role()) { switch(Auth::user()->role()) {
// Wholesalers are site admins, they can see everything // Wholesalers are site admins, they can see everything
@ -1267,10 +1169,11 @@ class Service extends Model implements IDs
} }
/** /**
* Has the price for this service been override * Has the price for this service been overridden
*
* @return bool * @return bool
*/ */
public function isChargeOverriden(): bool public function isChargeOverridden(): bool
{ {
return ! is_null($this->price); return ! is_null($this->price);
} }
@ -1293,9 +1196,9 @@ class Service extends Model implements IDs
*/ */
public function isPending(): bool public function isPending(): bool
{ {
return ! $this->active return (! $this->active)
AND ! is_null($this->order_status) && (! is_null($this->order_status))
AND ! in_array($this->order_status,array_merge(self::INACTIVE_STATUS,['INACTIVE'])); && (! in_array($this->order_status,array_merge(self::INACTIVE_STATUS,['INACTIVE'])));
} }
/** /**
@ -1392,6 +1295,18 @@ class Service extends Model implements IDs
return $this->invoice_items->filter(function($item) { return ! $item->exists; }); return $this->invoice_items->filter(function($item) { return ! $item->exists; });
} }
/**
* Extract data from the order_info column
*
* @param string $key
* @return string|null
* @todo This should be a json column, so we shouldnt need this by using order_info->key
*/
private function orderInfo(string $key): ?string
{
return $this->order_info ? $this->order_info->get($key) : NULL;
}
/** /**
* Service that was cancelled or never provisioned * Service that was cancelled or never provisioned
* *

View File

@ -4,6 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
/** /**
@ -68,15 +69,19 @@ class Site extends Model
/** /**
* Return the site address as an array * Return the site address as an array
* *
* @return array * @return Collection
*/ */
public function getAddressAttribute(): array public function getAddressAttribute(): Collection
{ {
return array_filter([ return collect([
$this->site_address1, 'address1' => $this->site_address1,
$this->site_address2, 'address2' => $this->site_address2,
sprintf('%s %s %s',$this->site_city.(($this->site_state OR $this->site_postcode) ? ',' : ''),$this->site_state,$this->site_postcode) 'location' => sprintf('%s %s %s',
]); $this->site_city.(($this->site_state || $this->site_postcode) ? ',' : ''),
$this->site_state,
$this->site_postcode)
])
->filter();
} }
/** /**

View File

@ -7,6 +7,7 @@ use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Collection as DatabaseCollection; use Illuminate\Database\Eloquent\Collection as DatabaseCollection;
use Illuminate\Support\Facades\Log;
use Laravel\Passport\HasApiTokens; use Laravel\Passport\HasApiTokens;
use Leenooks\Traits\ScopeActive; use Leenooks\Traits\ScopeActive;
use Leenooks\Traits\UserSwitch; use Leenooks\Traits\UserSwitch;
@ -142,6 +143,18 @@ class User extends Authenticatable implements IDs
/* ATTRIBUTES */ /* ATTRIBUTES */
/**
* Logged in user's full name
*
* @return string
* @deprecated use getNameFullAttribute()
*/
public function getFullNameAttribute(): string
{
Log::alert('UMO:! Deprecated function getFullNameAttribute()');
return $this->getNameFullAttribute();
}
/** /**
* This is an alias method, as it is used by the framework * This is an alias method, as it is used by the framework
* *
@ -149,7 +162,7 @@ class User extends Authenticatable implements IDs
*/ */
public function getNameAttribute(): string public function getNameAttribute(): string
{ {
return $this->full_name; return $this->name_full;
} }
/** /**
@ -157,11 +170,21 @@ class User extends Authenticatable implements IDs
* *
* @return string * @return string
*/ */
public function getFullNameAttribute(): string public function getNameFullAttribute(): string
{ {
return sprintf('%s %s',$this->firstname,$this->lastname); return sprintf('%s %s',$this->firstname,$this->lastname);
} }
/**
* Return the name, surname first
*
* @return string
*/
public function getNameSurFirstAttribute()
{
return sprintf('%s, %s',$this->lastname,$this->firstname);
}
/** /**
* Return a friendly string of this persons role * Return a friendly string of this persons role
* *
@ -172,9 +195,15 @@ class User extends Authenticatable implements IDs
return ucfirst($this->role()); return ucfirst($this->role());
} }
/**
* Return the name, surname first
* @return string
* @deprecated use getNameSurFirstAttribute()
*/
public function getSurFirstNameAttribute() public function getSurFirstNameAttribute()
{ {
return sprintf('%s, %s',$this->lastname,$this->firstname); Log::alert('UMO:! Deprecated function getSurFirstNameAttribute()');
return $this->getNameSurFirstAttribute();
} }
/* SCOPES */ /* SCOPES */
@ -189,27 +218,27 @@ class User extends Authenticatable implements IDs
{ {
// Build our where clause // Build our where clause
// First Name, Last name // First Name, Last name
if (preg_match('/\ /',$term)) { if (preg_match('/\s+/',$term)) {
[$fn,$ln] = explode(' ',$term,2); [$fn,$ln] = explode(' ',$term,2);
$query->where(function($query1) use ($fn,$ln,$term) { $query->where(function($query1) use ($fn,$ln,$term) {
$query1->where(function($query2) use ($fn,$ln) { $query1->where(function($query2) use ($fn,$ln) {
return $query2 return $query2
->where('firstname','like','%'.$fn.'%') ->where('firstname','ilike','%'.$fn.'%')
->where('lastname','like','%'.$ln.'%'); ->where('lastname','ilike','%'.$ln.'%');
}); });
}); });
} elseif (is_numeric($term)) { } elseif (is_numeric($term)) {
$query->where('users.id','like','%'.$term.'%'); $query->where('users.id','like','%'.$term.'%');
} elseif (preg_match('/\@/',$term)) { } elseif (preg_match('/@/',$term)) {
$query->where('email','like','%'.$term.'%'); $query->where('email','ilike','%'.$term.'%');
} else { } else {
$query $query
->Where('firstname','like','%'.$term.'%') ->Where('firstname','ilike','%'.$term.'%')
->orWhere('lastname','like','%'.$term.'%'); ->orWhere('lastname','ilike','%'.$term.'%');
} }
return $query; return $query;

View File

@ -1,17 +0,0 @@
<?php
/**
* Set defaults of QueryCacheable
*/
namespace App\Traits;
use Rennokki\QueryCache\Traits\QueryCacheable;
trait QueryCacheableConfig
{
use QueryCacheable;
public $cacheFor = 3600*6; // cache time, in seconds
protected static $flushCacheOnUpdate = TRUE;
public $cacheDriver = 'memcached';
}

View File

@ -54,6 +54,6 @@ trait ServiceDomains
// Build our where clause // Build our where clause
return parent::scopeSearch($query,$term) return parent::scopeSearch($query,$term)
->orwhere('domain_name','like','%'.$term.'%'); ->orwhere('domain_name','ilike','%'.$term.'%');
} }
} }

View File

@ -31,7 +31,7 @@ return new class extends Migration
$table->text('prod_attr')->nullable(); $table->text('prod_attr')->nullable();
$table->string('model')->nullable(); $table->string('model')->nullable();
$table->string('order_status')->nullable(); $table->string('order_status');
$table->text('order_info')->nullable(); $table->text('order_info')->nullable();
$table->date('invoice_last_at')->nullable(); $table->date('invoice_last_at')->nullable();

View File

@ -1,5 +1,5 @@
@component('mail::message',['site'=>$site,'heading'=>'Traffic Mismatch: '.$date]) @component('mail::message',['site'=>$site,'heading'=>'Traffic Mismatch: '.$date])
Hi {{ isset($user) ? $user->full_name.',' : '' }} Hi {{ isset($user) ? $user->name_full.',' : '' }}
The traffic import today, had mismatching records. A request to have those login removed has been generated. The traffic import today, had mismatching records. A request to have those login removed has been generated.

View File

@ -1,5 +1,5 @@
@component('mail::message',['site'=>$site,'heading'=>'Link Your Account']) @component('mail::message',['site'=>$site,'heading'=>'Link Your Account'])
Hi {{ isset($user) ? $user->full_name.',' : '' }} Hi {{ isset($user) ? $user->name_full.',' : '' }}
A request was made to link your account to a social login. A request was made to link your account to a social login.
If you didnt make this request, you can ignore this, and the request will be ignored. If you didnt make this request, you can ignore this, and the request will be ignored.

View File

@ -1,5 +1,5 @@
@component('mail::message',['site'=>$site,'heading'=>'System Test Email']) @component('mail::message',['site'=>$site,'heading'=>'System Test Email'])
Hi {{ isset($user) ? $user->full_name.',' : '' }} Hi {{ isset($user) ? $user->name_full.',' : '' }}
This is just a test email to validate that you can receive emails from us. This is just a test email to validate that you can receive emails from us.

View File

@ -1,5 +1,5 @@
@component('mail::message',['site'=>$site,'heading'=>'Invoice: '.$invoice->id]) @component('mail::message',['site'=>$site,'heading'=>'Invoice: '.$invoice->id])
Hi {{ isset($user) ? $user->full_name.',' : '' }} Hi {{ isset($user) ? $user->name_full.',' : '' }}
A new invoice has been generated on your account. A summary of that invoice is below. A new invoice has been generated on your account. A summary of that invoice is below.

View File

@ -1,5 +1,5 @@
@component('mail::message',['site'=>$site,'heading'=>'Password Reset']) @component('mail::message',['site'=>$site,'heading'=>'Password Reset'])
Hi {{ isset($user) ? $user->full_name.',' : '' }} Hi {{ isset($user) ? $user->name_full.',' : '' }}
You are receiving this email because we received a password reset request for your account. You are receiving this email because we received a password reset request for your account.

View File

@ -4,11 +4,11 @@
{{ $o->role }} Home {{ $o->role }} Home
@endsection @endsection
@section('page_title') @section('page_title')
{{ $o->full_name }} {{ $o->name_full }}
@endsection @endsection
@section('contentheader_title') @section('contentheader_title')
{{ $o->full_name }} {{ $o->name_full }}
@endsection @endsection
@section('contentheader_description') @section('contentheader_description')
{{ $o->role }} {{ $o->role }}

View File

@ -34,22 +34,20 @@
<!-- info row --> <!-- info row -->
<div class="row invoice-info"> <div class="row invoice-info">
<div class="col-4 invoice-col"> <div class="col-4 invoice-col">
FROM:
<address> <address>
<strong>{{ $site->site_name }}</strong><br> FROM: <strong>{{ $site->site_name }}</strong><br>
{!! join('<br>',$site->address) !!} {!! $site->address->join('<br>') !!}
<br> <br><br>
<strong>Email:</strong> {{ $site->site_email }}<br> <strong>Email:</strong> {{ $site->site_email }}<br>
<strong>Phone:</strong> {{ $site->site_phone }} <strong>Phone:</strong> {{ $site->site_phone }}
</address> </address>
</div> </div>
<div class="col-4 invoice-col"> <div class="col-4 invoice-col">
TO:
<address> <address>
<strong>{{ $o->account->company }}</strong><br> TO: <strong>{{ $o->account->company }}</strong><br>
{!! join('<br>',$o->account->address) !!} {!! $o->account->address->join('<br>') !!}
<br> <br><br>
<strong>Email:</strong> {{ $o->account->user->email }}<br> <strong>Email:</strong> {{ $o->account->user->email }}<br>
@if ($o->account->phone) @if ($o->account->phone)
<strong>Phone:</strong> {{ $o->account->phone }}<br> <strong>Phone:</strong> {{ $o->account->phone }}<br>

View File

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

View File

@ -33,7 +33,7 @@
@if (! $o->suspend_billing AND ! $o->external_billing) @if (! $o->suspend_billing AND ! $o->external_billing)
<li class="nav-item"><a class="nav-link {{ (! session()->has('service_update')) ? 'active' : '' }}" href="#pending_items" data-toggle="tab">Pending Items</a></li> <li class="nav-item"><a class="nav-link {{ (! session()->has('service_update')) ? 'active' : '' }}" href="#pending_items" data-toggle="tab">Pending Items</a></li>
@endif @endif
@if ($o->hasUsage()) @if ($o->product->hasUsage())
<li class="nav-item"><a class="nav-link {{ (! $o->isBilled() && (! session()->has('service_update'))) ? 'active' : '' }}" href="#traffic" data-toggle="tab">Traffic</a></li> <li class="nav-item"><a class="nav-link {{ (! $o->isBilled() && (! session()->has('service_update'))) ? 'active' : '' }}" href="#traffic" data-toggle="tab">Traffic</a></li>
@endif @endif
@ -74,7 +74,7 @@
@include('theme.backend.adminlte.service.widget.invoice') @include('theme.backend.adminlte.service.widget.invoice')
</div> </div>
@endif @endif
@if ($o->hasUsage()) @if ($o->product->hasUsage())
<div class="tab-pane fade {{ (! $o->isBilled() && (! session()->has('service_update'))) ? 'active show' : '' }}" id="traffic" role="tabpanel"> <div class="tab-pane fade {{ (! $o->isBilled() && (! session()->has('service_update'))) ? 'active show' : '' }}" id="traffic" role="tabpanel">
@if ($o->type->usage(30)->count()) @if ($o->type->usage(30)->count())
@include('theme.backend.adminlte.service.widget.'.$o->product->category.'.usagegraph',['o'=>$o->type]) @include('theme.backend.adminlte.service.widget.'.$o->product->category.'.usagegraph',['o'=>$o->type])

View File

@ -1,3 +1,4 @@
<!-- $o=Service::class -->
<table class="table table-sm" id="svc_bill_hist"> <table class="table table-sm" id="svc_bill_hist">
<thead> <thead>
<tr> <tr>

View File

@ -20,7 +20,7 @@
</tr> </tr>
<tr> <tr>
<th>Status</th> <th>Status</th>
<td>{!! $o->status_html !!}</td> <td>@include('theme.backend.adminlte.service.widget.status')</td>
</tr> </tr>
@if ($o->order_status == 'ORDER-SENT') @if ($o->order_status == 'ORDER-SENT')
<tr> <tr>
@ -47,7 +47,7 @@
</tr> </tr>
<tr> <tr>
<th>Amount</th> <th>Amount</th>
@if($o->isChargeOverriden()) @if($o->isChargeOverridden())
<td>@if ($o->billing_charge < $o->charge_orig)<del>${{ number_format($o->charge_orig,2) }}</del> @endif${{ number_format($o->billing_charge,2) }}</td> <td>@if ($o->billing_charge < $o->charge_orig)<del>${{ number_format($o->charge_orig,2) }}</del> @endif${{ number_format($o->billing_charge,2) }}</td>
@else @else
<td>${{ number_format($o->billing_charge,2) }}</td> <td>${{ number_format($o->billing_charge,2) }}</td>

View File

@ -61,11 +61,11 @@
<tr> <tr>
<th>Billing Price</th> <th>Billing Price</th>
<td @if($o->isChargeOverriden())class="text-danger"@endif>${{ number_format($b=$o->billing_charge,2) }}</td> <td @if($o->isChargeOverridden())class="text-danger"@endif>${{ number_format($b=$o->billing_charge,2) }}</td>
<td>${{ number_format($a=$o->account->taxed($c->base_cost),2) }}</td> <td>${{ number_format($a=$o->account->taxed($c->base_cost),2) }}</td>
<td>{!! markup($a,$b) !!}</td> <td>{!! markup($a,$b) !!}</td>
@if ($p->exists) @if ($p->exists)
<td @if($o->isChargeOverriden())class="text-danger"@endif>${{ number_format($b=$o->account->taxed($p->base_charge),2) }}</td> <td @if($o->isChargeOverridden())class="text-danger"@endif>${{ number_format($b=$o->account->taxed($p->base_charge),2) }}</td>
<td>${{ number_format($a=$o->account->taxed($p->base_cost),2) }}</td> <td>${{ number_format($a=$o->account->taxed($p->base_cost),2) }}</td>
<td>{!! markup($a,$b) !!}</td> <td>{!! markup($a,$b) !!}</td>
@endif @endif
@ -73,7 +73,7 @@
<tr> <tr>
<th>Monthly Price</th> <th>Monthly Price</th>
<td @if($x=$o->isChargeOverriden()) 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)*\App\Models\Invoice::billing_change($o->billing_interval,\App\Models\Invoice::BILL_MONTHLY),2) }}">${{ number_format($b=$o->billing_monthly_price,2) }}
@else @else
@ -83,7 +83,7 @@
<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)*\App\Models\Invoice::billing_change($o->billing_interval,\App\Models\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->isChargeOverriden()) 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)*\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)*\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)*\App\Models\Invoice::billing_change($o->billing_interval,\App\Models\Invoice::BILL_MONTHLY),2) }}</td>
<td>{!! markup($a,$b) !!}</td> <td>{!! markup($a,$b) !!}</td>
@endif @endif

View File

@ -23,7 +23,7 @@
@component('mail::footer') @component('mail::footer')
<div class="fixedw" style="text-align: right; font-size: 0.8em;"> <div class="fixedw" style="text-align: right; font-size: 0.8em;">
{{ config('mail.from.name') }}<br> {{ config('mail.from.name') }}<br>
{!! join('<br>',$site->address) !!}<br> {!! $site->address->join('<br>') !!}<br>
{{ $site->site_email }} {{ $site->site_email }}
</div> </div>
@endcomponent @endcomponent

View File

@ -26,7 +26,7 @@
<h2 style="text-align: right;">Our Contact Details</h2> <h2 style="text-align: right;">Our Contact Details</h2>
<address class="margin-bottom-40" style="float: right;"> <address class="margin-bottom-40" style="float: right;">
<table> <table>
<tr><th style="vertical-align:top; padding-right: 5px;">Address</th><td>{!! join('<br>',$site->address) !!}</td></tr> <tr><th style="vertical-align:top; padding-right: 5px;">Address</th><td>{!! $site->address->join('<br>') !!}</td></tr>
@isset($site->site_fax) @isset($site->site_fax)
<tr><th>Fax</th><td>{{ $site->site_fax }}</tr> <tr><th>Fax</th><td>{{ $site->site_fax }}</tr>
@endif @endif