From eb316f65fc32697e0594575205c3db20353aad33 Mon Sep 17 00:00:00 2001 From: Deon George Date: Sat, 8 Feb 2020 22:51:50 +1100 Subject: [PATCH] Next/Future invoices for users --- app/Http/Controllers/SearchController.php | 11 +- app/Http/Controllers/UserHomeController.php | 7 +- app/Models/Account.php | 3 - app/Models/Invoice.php | 2 +- app/Models/InvoiceItem.php | 12 +- app/Models/Service.php | 9 +- app/Models/Service/Adsl.php | 2 +- app/Models/Service/Voip.php | 23 +- app/User.php | 208 ++++++++++++++---- composer.lock | 6 +- config/app.php | 1 + .../common/invoice/widget/list.blade.php | 19 +- .../adminlte/r/account/widget/list.blade.php | 4 +- .../theme/backend/adminlte/r/home.blade.php | 2 +- .../r/service/widget/movement.blade.php | 4 +- .../theme/backend/adminlte/u/home.blade.php | 56 ++++- .../widgets/broadband/details.blade.php | 2 +- .../u/service/widgets/information.blade.php | 2 +- 18 files changed, 277 insertions(+), 96 deletions(-) diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 0ccdf39..93ddc86 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -27,7 +27,7 @@ class SearchController extends Controller $accounts = ($x=Auth::user()->all_accounts())->pluck('id'); $users = $x->transform(function($item) { return $item->user;}); - # Look for Account + # Look for User foreach (User::Search($request->input('term')) ->whereIN('id',$users->pluck('id')) ->orderBy('lastname') @@ -37,6 +37,15 @@ class SearchController extends Controller $result->push(['label'=>sprintf('US:%s %s',$o->aid,$o->name),'value'=>'/u/home/'.$o->id]); } + # Look for Account + foreach (Account::Search($request->input('term')) + ->whereIN('user_id',$users->pluck('id')) + ->orderBy('company') + ->limit(10)->get() as $o) + { + $result->push(['label'=>sprintf('AC:%s %s',$o->aid,$o->company),'value'=>'/u/home/'.$o->user_id]); + } + # Look for a Service foreach (Service::Search($request->input('term')) ->whereIN('account_id',$accounts) diff --git a/app/Http/Controllers/UserHomeController.php b/app/Http/Controllers/UserHomeController.php index 9676863..88d9604 100644 --- a/app/Http/Controllers/UserHomeController.php +++ b/app/Http/Controllers/UserHomeController.php @@ -24,8 +24,11 @@ class UserHomeController extends Controller */ public function home(User $o=NULL): View { - if (is_null($o)) - $o = Auth::user(); + if ($o) + return View('u.home',['o'=>$o]); + + // If User was null, then test and see what type of logged on user we have + $o = Auth::user(); switch (Auth::user()->role()) { case 'customer': diff --git a/app/Models/Account.php b/app/Models/Account.php index 6973f3c..05f9a26 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -2,7 +2,6 @@ namespace App\Models; -use App\User; use Illuminate\Database\Eloquent\Model; use App\Traits\NextKey; @@ -10,9 +9,7 @@ use App\Traits\NextKey; class Account extends Model { use NextKey; - const RECORD_ID = 'account'; - public $incrementing = FALSE; protected $table = 'ab_account'; diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 6d2a460..f9dddf2 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -8,7 +8,7 @@ class Invoice extends Model { protected $table = 'ab_invoice'; protected $dates = ['date_orig','due_date']; - protected $with = ['account.country.currency','items','paymentitems']; + protected $with = ['account.country.currency','items.taxes','paymentitems']; protected $appends = [ 'date_due', diff --git a/app/Models/InvoiceItem.php b/app/Models/InvoiceItem.php index 6d34d16..d589c1a 100644 --- a/app/Models/InvoiceItem.php +++ b/app/Models/InvoiceItem.php @@ -2,8 +2,10 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; + use Leenooks\Carbon; class InvoiceItem extends Model @@ -11,7 +13,6 @@ class InvoiceItem extends Model protected $dates = ['date_start','date_stop']; public $dateFormat = 'U'; protected $table = 'ab_invoice_item'; - protected $with = ['taxes']; private $_tax = 0; @@ -20,6 +21,11 @@ class InvoiceItem extends Model return $this->belongsTo(Invoice::class); } + /** + * Product for this item + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ public function product() { return $this->belongsTo(Product::class); @@ -131,13 +137,13 @@ class InvoiceItem extends Model /** * Add taxes to this record */ - public function addTaxes() + public function addTaxes(Collection $taxes) { // Refuse to change an existing record if ($this->exists) throw new \Exception('Refusing to add Taxes to existing record'); - foreach($this->service->account->country->taxes as $to) + foreach($taxes as $to) { $iit = new InvoiceItemTax; $iit->tax_id = $to->id; diff --git a/app/Models/Service.php b/app/Models/Service.php index 7eef306..9640f54 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -191,9 +191,6 @@ class Service extends Model */ public function type() { - if (! $this->model) - abort(500,'Missing Model in',['service'=>$this]); - return $this->morphTo(null,'model','id','service_id'); } @@ -772,7 +769,7 @@ class Service extends Model $o->date_stop = $this->invoice_next_end; $o->quantity = $this->invoice_next_quantity; - $o->addTaxes(); + $o->addTaxes($this->account->country->taxes); $result->push($o); } @@ -789,7 +786,7 @@ class Service extends Model $o->date_stop = $this->invoice_next; $o->quantity = 1; - $o->addTaxes(); + $o->addTaxes($this->account->country->taxes); $result->push($o); } @@ -807,7 +804,7 @@ class Service extends Model $o->module_id = 30; // @todo This shouldnt be hard coded $o->module_ref = $oo->id; - $o->addTaxes(); + $o->addTaxes($this->account->country->taxes); $result->push($o); } diff --git a/app/Models/Service/Adsl.php b/app/Models/Service/Adsl.php index dca9b77..f0b9118 100644 --- a/app/Models/Service/Adsl.php +++ b/app/Models/Service/Adsl.php @@ -68,7 +68,7 @@ class Adsl extends ServiceType implements ServiceItem */ public function getServiceDescriptionAttribute(): string { - return $this->service_address ?: 'NO Service Address'; + return strtoupper($this->service_address) ?: 'NO Service Address'; } /** diff --git a/app/Models/Service/Voip.php b/app/Models/Service/Voip.php index 12c697e..4dd7e6d 100644 --- a/app/Models/Service/Voip.php +++ b/app/Models/Service/Voip.php @@ -2,24 +2,33 @@ namespace App\Models\Service; +use App\Interfaces\ServiceItem; +use App\Models\Base\ServiceType; use App\Traits\NextKey; -class Voip extends \App\Models\Base\ServiceType +class Voip extends ServiceType implements ServiceItem { use NextKey; - const RECORD_ID = 'service__adsl'; protected $table = 'ab_service__voip'; - public function getFullNameAttribute() + /** + * Return the service address + * + * @return string + */ + public function getServiceDescriptionAttribute(): string { - return ($this->service_number AND $this->service_address) - ? sprintf('%s: %s',$this->service_number, $this->service_address) - : $this->name; + return $this->service_address ?: 'VOIP'; } - public function getNameAttribute() + /** + * Return the service number + * + * @return string + */ + public function getServiceNameAttribute(): string { return $this->service_number; } diff --git a/app/User.php b/app/User.php index 970849e..35a017f 100644 --- a/app/User.php +++ b/app/User.php @@ -2,7 +2,6 @@ namespace App; -use App\Models\Site; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Support\Collection; @@ -12,32 +11,38 @@ use Laravel\Passport\HasApiTokens; use Leenooks\Carbon; use Leenooks\Traits\UserSwitch; use App\Notifications\ResetPassword as ResetPasswordNotification; +use App\Models\Site; use App\Models\Service; use Spinen\QuickBooks\HasQuickBooksToken; class User extends Authenticatable { - use HasApiTokens,Notifiable,UserSwitch,HasQuickBooksToken; + use HasApiTokens,Notifiable,UserSwitch,HasQuickBooksToken; - protected $dates = ['created_at','updated_at','last_access']; + protected $dates = [ + 'created_at', + 'updated_at', + 'last_access' + ]; - /** - * The attributes that are mass assignable. - * - * @var array - */ - protected $fillable = [ - 'name', 'email', 'password', - ]; + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'name', 'email', 'password', + ]; - /** - * The attributes that should be hidden for arrays. - * - * @var array - */ - protected $hidden = [ - 'password', 'remember_token', - ]; + /** + * The attributes that should be hidden for arrays. + * + * @var array + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; protected $appends = [ 'active_display', @@ -57,49 +62,92 @@ class User extends Authenticatable 'user_id_url', ]; + /** + * The accounts that this user manages + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ public function accounts() - { + { return $this->hasMany(Models\Account::class); - } + } + /** + * The agents that this users manages + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ public function agents() { return $this->hasMany(static::class,'parent_id','id')->with('agents'); } + /** + * The clients that this user has + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ public function clients() { - return $this->hasMany(static::class,'parent_id','id')->with('clients'); + return $this + ->hasMany(static::class,'parent_id','id') + ->with('clients'); } + /** + * This users language configuration + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ public function language() { return $this->belongsTo(Models\Language::class); } + /**b + * This users invoices + * + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ public function invoices() { return $this->hasManyThrough(Models\Invoice::class,Models\Account::class); } + /** + * The payments this user has made + * + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ public function payments() { return $this->hasManyThrough(Models\Payment::class,Models\Account::class); } - public function site() - { - return $this->belongsTo(Site::class); - } - + /** + * THe services this user has + * + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ public function services() { - return $this->hasManyThrough(Models\Service::class,Models\Account::class); + return $this->hasManyThrough(Models\Service::class,Models\Account::class) + ->with(['account','product','invoices.items.tax','type']); } + /** + * This users supplier/reseller + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ protected function supplier() { return $this->belongsTo(static::class,'parent_id','id'); } + /** + * Who this user supplies to + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ protected function suppliers() { return $this->hasMany(static::class,'parent_id','id'); } @@ -116,11 +164,16 @@ class User extends Authenticatable * * @return string */ - public function getFullNameAttribute() + public function getFullNameAttribute(): string { return sprintf('%s %s',$this->firstname,$this->lastname); } + /** + * A list of all invoices currently unpaid + * + * @return mixed + */ public function getInvoicesDueAttribute() { return $this->invoices @@ -131,9 +184,17 @@ class User extends Authenticatable ->filter(); } + /** + * Get the users language + * + * For non logged in users we need to populate with a default language + * @param $value + * @return mixed + * @todo This doesnt appear to be used? + */ public function getLanguageAttribute($value) { - if (is_null($this->language_id)) + if (is_null($value)) return config('SITE_SETUP')->language; } @@ -141,7 +202,8 @@ class User extends Authenticatable * Return a Carbon Date if it has a value. * * @param $value - * @return \Leenooks\Carbon + * @return Carbon + * @throws \Exception * @todo This attribute is not in the schema */ public function getLastAccessAttribute($value) @@ -159,6 +221,12 @@ class User extends Authenticatable return $this->full_name; } + /** + * Return a list of the payments that the user has made + * + * @return mixed + * @todo Merge this with payments() + */ public function getPaymentHistoryAttribute() { return $this->payments @@ -166,11 +234,18 @@ class User extends Authenticatable ->reverse(); } + /** + * The users active services + * + * @return mixed + */ public function getServicesActiveAttribute() { - return $this->services->filter(function($item) { - return $item->isActive(); - }); + return $this->services + ->filter(function($item) + { + return $item->isActive(); + }); } public function getServicesCountHtmlAttribute() @@ -198,6 +273,11 @@ class User extends Authenticatable return sprintf('%s',$this->id,$this->user_id); } + /** + * Users password reset email notification + * + * @param string $token + */ public function sendPasswordResetNotification($token) { $this->notify((new ResetPasswordNotification($token))->onQueue('high')); @@ -213,7 +293,7 @@ class User extends Authenticatable /** * Search for a record * - * @param $query + * @param $query * @param string $term * @return */ @@ -253,12 +333,12 @@ class User extends Authenticatable * @param $id * @return bool */ - public function isAdmin($id) + public function isAdmin($id): bool { return $id AND $this->isReseller() AND in_array($id,$this->all_accounts()->pluck('id')->toArray()); } - /** Functions */ + /** FUNCTIONS */ /** * Get a list of accounts for the clients of this user @@ -337,13 +417,17 @@ class User extends Authenticatable }); } - // List all the agents, including agents of agents + /** + * List of all this users agents, recursively + * + * @param int $level + * @return Collection + */ public function all_agents($level=0) { $result = collect(); - foreach ($this->agents as $o) - { + foreach ($this->agents as $o) { if (! $o->active OR ! $o->agents->count()) continue; @@ -363,16 +447,56 @@ class User extends Authenticatable * * @return bool */ - public function isReseller() + public function isReseller(): bool { return in_array($this->role(),['wholesaler','reseller']); } - public function isWholesaler() + /** + * Determine if the logged in user is a wholesaler + * + * @return bool + */ + public function isWholesaler(): bool { return in_array($this->role(),['wholesaler']); } + /** + * Get all the items for the next invoice + * + * @return Collection + */ + public function next_invoice_items(bool $future=FALSE): DatabaseCollection + { + $result = new DatabaseCollection; + $this->load([ + 'services.charges', + 'services.invoice_items' + ]); + + foreach ($this->services as $o) { + if ($future) { + if ($o->invoice_next->subDays(config('app.invoice_inadvance'))->isPast()) + continue; + + } else { + if ($o->invoice_next->subDays(config('app.invoice_inadvance'))->isFuture()) + continue; + } + + foreach ($o->next_invoice_items() as $oo) + $result->push($oo); + } + + $result->load([ + 'product.descriptions', + 'service.type', + ]); + + return $result; + } + public function role() { // If I have agents and no parent, I am the wholesaler diff --git a/composer.lock b/composer.lock index aa79149..7bd0e2e 100644 --- a/composer.lock +++ b/composer.lock @@ -2336,11 +2336,11 @@ }, { "name": "leenooks/laravel", - "version": "6.0.14", + "version": "6.0.15", "source": { "type": "git", "url": "https://dev.leenooks.net/leenooks/laravel", - "reference": "176f680ff740d41b6e66723557e2e64721e0d51f" + "reference": "96a6830e61a612dbff396a041bc0263f2308a324" }, "require": { "creativeorange/gravatar": "^1.0", @@ -2377,7 +2377,7 @@ "laravel", "leenooks" ], - "time": "2020-01-22T09:48:42+00:00" + "time": "2020-02-08T06:52:13+00:00" }, { "name": "monolog/monolog", diff --git a/config/app.php b/config/app.php index 56d93ed..de385cd 100644 --- a/config/app.php +++ b/config/app.php @@ -242,4 +242,5 @@ return [ ], + 'invoice_inadvance'=>30, ]; diff --git a/resources/theme/backend/adminlte/common/invoice/widget/list.blade.php b/resources/theme/backend/adminlte/common/invoice/widget/list.blade.php index 2c0d5a5..ad2f0c3 100644 --- a/resources/theme/backend/adminlte/common/invoice/widget/list.blade.php +++ b/resources/theme/backend/adminlte/common/invoice/widget/list.blade.php @@ -9,6 +9,7 @@ Outstanding + @foreach ($o->invoices as $io) @@ -32,15 +33,15 @@ @js('/plugin/dataTables/dataTables.bootstrap4.js','dt-bootstrap4-js','jq-dt-js') @append \ No newline at end of file diff --git a/resources/theme/backend/adminlte/r/account/widget/list.blade.php b/resources/theme/backend/adminlte/r/account/widget/list.blade.php index 1acb0ae..1897641 100644 --- a/resources/theme/backend/adminlte/r/account/widget/list.blade.php +++ b/resources/theme/backend/adminlte/r/account/widget/list.blade.php @@ -33,8 +33,8 @@ @section('page-scripts') - @css('//cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css','jq-dt-css','jquery'); - @js('//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js','jq-dt-js','jquery'); + @css('//cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css','jq-dt-css','jquery') + @js('//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js','jq-dt-js','jquery') @css('//cdn.datatables.net/responsive/2.2.1/css/responsive.dataTables.min.css','dt-responsive-css','jq-dt-css') @js('//cdn.datatables.net/responsive/2.2.1/js/dataTables.responsive.min.js','dt-responsive-js','jq-dt-js') @css('/plugin/dataTables/dataTables.bootstrap4.css','dt-bootstrap4-css','jq-dt-css') diff --git a/resources/theme/backend/adminlte/r/home.blade.php b/resources/theme/backend/adminlte/r/home.blade.php index 898028a..818dc4e 100644 --- a/resources/theme/backend/adminlte/r/home.blade.php +++ b/resources/theme/backend/adminlte/r/home.blade.php @@ -22,7 +22,6 @@
-
--}} +
diff --git a/resources/theme/backend/adminlte/r/service/widget/movement.blade.php b/resources/theme/backend/adminlte/r/service/widget/movement.blade.php index 6bb6584..f7768d7 100644 --- a/resources/theme/backend/adminlte/r/service/widget/movement.blade.php +++ b/resources/theme/backend/adminlte/r/service/widget/movement.blade.php @@ -34,8 +34,8 @@ @section('page-scripts') - @css('//cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css','jq-dt-css','jquery'); - @js('//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js','jq-dt-js','jquery'); + @css('//cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css','jq-dt-css','jquery') + @js('//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js','jq-dt-js','jquery') @css('//cdn.datatables.net/responsive/2.2.1/css/responsive.dataTables.min.css','dt-responsive-css','jq-dt-css') @js('//cdn.datatables.net/responsive/2.2.1/js/dataTables.responsive.min.js','dt-responsive-js','jq-dt-js') @css('//cdn.datatables.net/rowgroup/1.0.2/css/rowGroup.dataTables.min.css','dt-rowgroup-css','jq-dt-css') diff --git a/resources/theme/backend/adminlte/u/home.blade.php b/resources/theme/backend/adminlte/u/home.blade.php index 3de3c0d..f3d37e9 100644 --- a/resources/theme/backend/adminlte/u/home.blade.php +++ b/resources/theme/backend/adminlte/u/home.blade.php @@ -15,19 +15,53 @@ @endsection @section('main-content') -
-
- @include('common.account.widget.summary') -
+
+ @include('common.account.widget.summary') +
-
-
- @include('common.service.widget.active') -
+
+
+
+ -
- @include('common.invoice.widget.due') - @include('common.payment.widget.history') +
+
+
+
+
+ @include('common.service.widget.active') +
+ +
+ @include('common.invoice.widget.due') + @include('common.payment.widget.history') +
+
+
+ +
+
+
+ @include('r.invoice.widget.next',['future'=>FALSE]) +
+
+
+ +
+
+
+ @include('r.invoice.widget.next',['future'=>TRUE]) +
+
+
+
+
diff --git a/resources/theme/backend/adminlte/u/service/widgets/broadband/details.blade.php b/resources/theme/backend/adminlte/u/service/widgets/broadband/details.blade.php index ed9141c..d077559 100644 --- a/resources/theme/backend/adminlte/u/service/widgets/broadband/details.blade.php +++ b/resources/theme/backend/adminlte/u/service/widgets/broadband/details.blade.php @@ -15,7 +15,7 @@ - + diff --git a/resources/theme/backend/adminlte/u/service/widgets/information.blade.php b/resources/theme/backend/adminlte/u/service/widgets/information.blade.php index 91940c6..db20cdb 100644 --- a/resources/theme/backend/adminlte/u/service/widgets/information.blade.php +++ b/resources/theme/backend/adminlte/u/service/widgets/information.blade.php @@ -18,7 +18,7 @@ - @if($o->active) + @if($o->active AND $o->invoice_to)
Address{{ $o->service_description }}{{ $o->service_description }}
Service NumberBilled {{ $o->billing_period }}
Invoiced To {{ $o->invoice_to->format('Y-m-d') }}