From c444e1d2186c179fa8487d1b1dac58b5a217b84d Mon Sep 17 00:00:00 2001 From: Deon George Date: Wed, 1 Aug 2018 17:09:38 +1000 Subject: [PATCH] Initial invoice rendering --- .env.example | 2 +- app/Http/Controllers/UserHomeController.php | 13 + app/Http/Middleware/SetSite.php | 6 +- app/Models/Account.php | 43 +++- app/Models/Charge.php | 15 ++ app/Models/Invoice.php | 83 +++++- app/Models/InvoiceItem.php | 59 +++++ app/Models/Product.php | 6 + app/Models/Service.php | 20 +- app/Models/Site.php | 5 + app/User.php | 17 ++ composer.lock | 6 +- config/app.php | 2 +- .../theme/backend/adminlte/invoice.blade.php | 243 ++++++++++++++++++ .../widgets/services_active.blade.php | 2 +- routes/web.php | 6 +- 16 files changed, 500 insertions(+), 28 deletions(-) create mode 100644 app/Models/Charge.php create mode 100644 resources/theme/backend/adminlte/invoice.blade.php diff --git a/.env.example b/.env.example index 5754aa0..5d8a5f6 100644 --- a/.env.example +++ b/.env.example @@ -3,7 +3,7 @@ APP_NAME_HTML_LONG="LaravelApplication" APP_NAME_HTML_SHORT="LA" APP_ENV=local APP_KEY= -APP_DEBUG=true +APP_DEBUG=false APP_URL=http://localhost LOG_CHANNEL=stack diff --git a/app/Http/Controllers/UserHomeController.php b/app/Http/Controllers/UserHomeController.php index 833a41e..7f95858 100644 --- a/app/Http/Controllers/UserHomeController.php +++ b/app/Http/Controllers/UserHomeController.php @@ -3,6 +3,8 @@ namespace App\Http\Controllers; use Illuminate\Support\Facades\Auth; +use App\Models\Invoice; +use App\User; class UserHomeController extends Controller { @@ -11,6 +13,11 @@ class UserHomeController extends Controller $this->middleware('auth'); } + public function invoice(Invoice $o) + { + return View('invoice',['o'=>$o]); + } + public function home() { switch (Auth::user()->role()) { @@ -41,4 +48,10 @@ class UserHomeController extends Controller { abort(307,sprintf('http://www.graytech.net.au/u/%s/%s/%s',$type,$action,$id)); } + + public function User(User $o) + { + // @todo Check authorised to see this account. + return View('userhome',['o'=>$o]); + } } \ No newline at end of file diff --git a/app/Http/Middleware/SetSite.php b/app/Http/Middleware/SetSite.php index 95514e7..e8391b4 100644 --- a/app/Http/Middleware/SetSite.php +++ b/app/Http/Middleware/SetSite.php @@ -4,10 +4,10 @@ namespace App\Http\Middleware; use Illuminate\Support\Facades\Schema; use Closure; -use App\Models\{Site}; use Config; use View; -use Theme; + +use App\Models\Site; /** * Class SetSite @@ -40,8 +40,6 @@ class SetSite $so = (new Site)->sample(); } - Theme::set($so->theme); - // Set who we are in SETUP. Config::set('SITE_SETUP',$so); View::share('so',$so); diff --git a/app/Models/Account.php b/app/Models/Account.php index 963d560..89f2ee1 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -36,13 +36,52 @@ class Account extends Model return $this->belongsTo(\App\User::class); } + public function getActiveDisplayAttribute($value) + { + return sprintf('%s',$this->active ? 'primary' : 'danger',$this->active ? 'Active' : 'Inactive'); + } + public function getCompanyAttribute($value) { return $value ? $value : $this->user->SurFirstName; } - public function getActiveDisplayAttribute($value) + public function getAccountIdAttribute() { - return sprintf('%s',$this->active ? 'primary' : 'danger',$this->active ? 'Active' : 'Inactive'); + return sprintf('%02s-%04s',$this->site_id,$this->id); + } + + public function getAccountIdUrlAttribute() + { + return sprintf('%s',$this->id,$this->account_id); + } + + private function _address() + { + $return = []; + + if ($this->address1) + array_push($return,$this->address1); + if ($this->address2) + array_push($return,$this->address2); + if ($this->city) + array_push($return,sprintf('%s %s %s',$this->city.(($this->state OR $this->zip) ? ',' : ''),$this->state,$this->zip)); + + if (! $return) + $return = ['No Address']; + + return $return; + } + + public function address($type='plain') + { + switch ($type) + { + case 'html' : return join('
',$this->_address()); + case 'newline': return join("\m",$this->_address()); + + default: + return join("\n",$this->_address()); + } } } \ No newline at end of file diff --git a/app/Models/Charge.php b/app/Models/Charge.php new file mode 100644 index 0000000..2616087 --- /dev/null +++ b/app/Models/Charge.php @@ -0,0 +1,15 @@ +description,join('|',unserialize($this->getAttribute('attributes')))); + } +} \ No newline at end of file diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 7122fb7..8117599 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -7,8 +7,8 @@ use Illuminate\Database\Eloquent\Model; class Invoice extends Model { protected $table = 'ab_invoice'; - protected $dates = ['due_date']; - protected $with = ['items.taxes','account.country.currency','paymentitems']; + protected $dates = ['date_orig','due_date']; + protected $with = ['items.taxes','items.product','items.service','account.country.currency','paymentitems']; protected $appends = [ 'date_due', @@ -26,6 +26,7 @@ class Invoice extends Model ]; private $_total = 0; + private $_total_tax = 0; public function account() { @@ -52,14 +53,29 @@ class Invoice extends Model return $this->due_date->format('Y-m-d'); } + public function getInvoiceDateAttribute() + { + return $this->date_orig->format('Y-m-d'); + } + public function getInvoiceIdAttribute() { - return sprintf('%02s-%04s-%04s',$this->site_id,$this->account_id,$this->id); + return sprintf('%06s',$this->id); + } + + public function getInvoiceAccountIdAttribute() + { + return sprintf('%02s-%04s-%06s',$this->site_id,$this->account_id,$this->invoice_id); } public function getInvoiceIdUrlAttribute() { - return sprintf('%s',$this->id,$this->invoice_id); + return sprintf('%s',$this->id,$this->invoice_account_id); + } + + public function getInvoiceTextAttribute() + { + return sprintf('Thank you for using %s for your Internet Services.',config('SITE_SETUP')->site_name); } public function getPaidAttribute() @@ -67,6 +83,25 @@ class Invoice extends Model return $this->currency()->round($this->paymentitems->sum('alloc_amt')); } + public function getSubTotalAttribute() + { + return sprintf('%3.'.$this->currency()->rounding.'f',$this->total-$this->tax_total); + } + + public function getTaxTotalAttribute() + { + if (! $this->_total_tax) + { + foreach ($this->items as $o) + { + if ($o->active) + $this->_total_tax += $this->currency()->round($o->tax); + } + } + + return sprintf('%3.'.$this->currency()->rounding.'f',$this->_total_tax); + } + public function getTotalAttribute() { if (! $this->_total) @@ -85,4 +120,44 @@ class Invoice extends Model { return $this->account->country->currency; } + + public function products() + { + $return = collect(); + + foreach ($this->items->groupBy('product_id') as $o) + { + $po = $o->first()->product; + $po->count = count($o->pluck('service_id')->unique()); + + $return->push($po); + } + + $lo = $this->account->user->language; + return $return->sortBy(function ($item) use ($lo) { + return $item->name($lo); + }); + } + + public function product_services(Product $po) + { + $return = collect(); + + foreach ($this->items->filter(function ($item) use ($po) { + return $item->product_id == $po->id; + }) as $o) + { + $so = $o->service; + $return->push($so); + }; + + return $return->unique()->sortBy('name'); + } + + public function product_service_items(Product $po,Service $so) + { + return $this->items->filter(function ($item) use ($po,$so) { + return $item->product_id == $po->id AND $item->service_id == $so->id; + })->filter()->sortBy('item_type'); + } } \ No newline at end of file diff --git a/app/Models/InvoiceItem.php b/app/Models/InvoiceItem.php index bcb94ad..6a29402 100644 --- a/app/Models/InvoiceItem.php +++ b/app/Models/InvoiceItem.php @@ -6,21 +6,80 @@ use Illuminate\Database\Eloquent\Model; class InvoiceItem extends Model { + protected $dates = ['date_start','date_stop']; protected $table = 'ab_invoice_item'; protected $with = ['taxes']; private $_tax = 0; + public function product() + { + return $this->belongsTo(Product::class); + } + public function invoice() { return $this->belongsTo(Invoice::class); } + public function service() + { + return $this->belongsTo(Service::class); + } + public function taxes() { return $this->hasMany(InvoiceItemTax::class); } + public function getItemTypeNameAttribute() + { + $types = [ + 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 + 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 + 120=>'Credit/Debit Transfer', // * SERVICE_ID is NULL, MODULE_ID is NULL, MODULE_REF is NULL : INVOICE_ID is NOT NULL + 123=>'Shipping', + 124=>'Late Payment Fee', // * SERVICE_ID is NULL, MODULE_ID = CHECKOUT MODULE, + 125=>'Payment Fee', // * SERVICE_ID is NULL, MODULE_ID = CHECKOUT MODULE, MODULE_REF = CHECKOUT NAME + 126=>'Other', // * MODEL_ID should be a module + 127=>'Rounding', // * SERVICE_ID is NULL, MODULE_ID is NULL, MODULE_REF is NULL + ]; + + switch ($this->item_type) + { + // * Line Charge Topic on Invoice. + case 0: + return sprintf('%s [%s]','Product/Service', + $this->date_start == $this->date_stop ? $this->date_start->format('Y-m-d') : sprintf('%s -> %s',$this->date_start->format('Y-m-d'),$this->date_stop->format('Y-m-d'))); + + // * Excess Service Item, of item 0, must have corresponding SERVICE_ID + case 5: + return sprintf('%s [%s] (%s)','Excess Usage', + $this->date_start == $this->date_stop ? $this->date_start->format('Y-m-d') : sprintf('%s -> %s',$this->date_start->format('Y-m-d'),$this->date_stop->format('Y-m-d')), + $this->module_text() + ); + + default: + return array_get($types,$this->item_type,'Unknown'); + } + } + + public function module_text() + { + switch ($this->module_id) + { + // Charges Module + case 30: return Charge::findOrFail($this->module_ref)->name; + + default: abort(500,'Unable to handle '.$this->module_id); + } + + } public function getSubTotalAttribute() { return $this->quantity * $this->price_base; diff --git a/app/Models/Product.php b/app/Models/Product.php index 41c8412..595a809 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -19,10 +19,16 @@ class Product extends Model return $this->hasMany(Service::class); } + public function getProductIdAttribute() + { + return sprintf('#%04s',$this->id); + } + /** * Get the language name * * @param Language $lo + * @return string Product Name */ public function name(Language $lo) { diff --git a/app/Models/Service.php b/app/Models/Service.php index 4151fda..080dd09 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -12,11 +12,11 @@ class Service extends Model protected $appends = [ 'category', + 'name', 'next_invoice', 'product_name', 'service_id', 'service_id_url', - 'service_name', 'status', ]; protected $visible = [ @@ -24,11 +24,11 @@ class Service extends Model 'category', 'data_orig', 'id', + 'name', 'next_invoice', 'product_name', 'service_id', 'service_id_url', - 'service_name', 'status', ]; @@ -80,6 +80,14 @@ class Service extends Model return $this->product->prod_plugin_file; } + public function getNameAttribute() + { + if (! isset($this->getServiceDetail()->name)) + return 'Unknown'; + + return $this->getServiceDetail()->name; + } + public function getNextInvoiceAttribute() { return $this->date_next_invoice ? $this->date_next_invoice->format('Y-m-d') : NULL; @@ -127,14 +135,6 @@ class Service extends Model return sprintf('%s',$this->id,$this->service_id); } - public function getServiceNameAttribute() - { - if (! isset($this->getServiceDetail()->name)) - return 'Unknown'; - - return $this->getServiceDetail()->name; - } - public function getServiceNumberAttribute() { return sprintf('%02s.%04s.%04s',$this->site_id,$this->account_id,$this->id); diff --git a/app/Models/Site.php b/app/Models/Site.php index ae925ef..dd9dab8 100644 --- a/app/Models/Site.php +++ b/app/Models/Site.php @@ -21,6 +21,11 @@ class Site extends Model return $this->hasMany(SiteDetails::class); } + public function language() + { + return $this->belongsTo(Language::class); + } + public function __get($key) { // @todo Not sure if this is functioning correctly? diff --git a/app/User.php b/app/User.php index f538ba9..f5db363 100644 --- a/app/User.php +++ b/app/User.php @@ -58,6 +58,11 @@ class User extends Authenticatable return $this->hasMany(static::class,'parent_id','id')->with('clients'); } + public function language() + { + return $this->belongsTo(Models\Language::class); + } + public function invoices() { return $this->hasManyThrough(Models\Invoice::class,Models\Account::class); @@ -120,6 +125,13 @@ class User extends Authenticatable ->filter(); } + public function getLanguageAttribute($value) + { + if (is_null($this->language_id)) + return config('SITE_SETUP')->language; + dd(__METHOD__,$value,config('SITE_SETUP')->language); + } + public function getPaymentHistoryAttribute() { return $this->payments @@ -152,6 +164,11 @@ class User extends Authenticatable return sprintf('%s',$this->id,$this->user_id); } + public function isAdmin($id) + { + return $id AND in_array($this->role(),['wholesaler','reseller']) AND in_array($id,$this->all_accounts()->pluck('id')->toArray()); + } + public function scopeActive() { return $this->where('active',TRUE); diff --git a/composer.lock b/composer.lock index 1dda2a6..6267aa8 100644 --- a/composer.lock +++ b/composer.lock @@ -2369,11 +2369,11 @@ }, { "name": "leenooks/laravel", - "version": "0.1.9", + "version": "0.1.10", "source": { "type": "git", "url": "https://dev.leenooks.net/leenooks/laravel", - "reference": "444c159ab911819e818a7f8a1f0aaf59dfdd58d1" + "reference": "0bd32aab4a53a7f74d76a77165f973026ce90444" }, "require": { "igaster/laravel-theme": "2.0.6", @@ -2409,7 +2409,7 @@ "laravel", "leenooks" ], - "time": "2018-07-31T02:56:29+00:00" + "time": "2018-08-01T06:28:10+00:00" }, { "name": "maximebf/debugbar", diff --git a/config/app.php b/config/app.php index 7ed8b7d..d4b1460 100644 --- a/config/app.php +++ b/config/app.php @@ -67,7 +67,7 @@ return [ | */ - 'timezone' => 'UTC', + 'timezone' => 'Australia/Melbourne', /* |-------------------------------------------------------------------------- diff --git a/resources/theme/backend/adminlte/invoice.blade.php b/resources/theme/backend/adminlte/invoice.blade.php new file mode 100644 index 0000000..49bc05d --- /dev/null +++ b/resources/theme/backend/adminlte/invoice.blade.php @@ -0,0 +1,243 @@ +@extends('adminlte::layouts.app') + +@section('htmlheader_title') + Invoice #{{ $o->id }} +@endsection + +@section('contentheader_title') + Invoice # +@endsection +@section('contentheader_description') + {{ $o->invoice_id }} +@endsection + +@section('main-content') + +
+ +
+
+ +
+ +
+ + +
+
+ + + + + + + + + + + + + + + + + +
FROM:{{ $so->site_name }}
 {!! $so->address('html') !!}
Phone{{ $so->site_phone }}
Email{{ $so->site_email }}
+
+ +
+ + + + + + + + + + + @if ($o->account->phone) + + + @else + + @endif + + + + + + + + + +
TO:{{ $o->account->company }}
 {!! $o->account->address('html') !!}
Phone{{ $so->site_phone }} 
Email{{ $o->account->email }}
Account{{ $o->account->account_id }}
+
+ +
+ + + + + + + + + + + + + +
Invoice #{{ $o->invoice_id }}
Payment Due{{ $o->due_date->format('Y-m-d') }}
Total${{ number_format($o->total,$o->currency()->rounding) }}
+
+
+ + + +
+
+ + + + + + + + + + + @foreach ($o->products() as $po) + + + + + + + + @foreach ($o->product_services($po) as $so) + + + + + + + + @foreach ($o->product_service_items($po,$so) as $io) + + + + + + + + @endforeach + @endforeach + @endforeach + +
QtyProductDescriptionSubtotal
{{ $po->count }}{{ $po->product_id }}{{ $po->name($o->account->user->language) }} ${{ number_format($o->items->filter(function($item) use ($po) {return $item->product_id == $po->id; })->sum('total'),$o->currency()->rounding) }}
 Service: {{ $so->service_id }}: {{ $so->name }} ${{ number_format($o->product_service_items($po,$so)->sum('total'),$o->currency()->rounding) }} 
  {{ $io->item_type_name }}${{ number_format($io->total,$o->currency()->rounding) }} 
+
+ +
+ + +
+ +
+

Payment Methods:

+ {{-- + Visa + Mastercard + American Express + Paypal + --}} + +

+ {{ $o->invoice_text }} +

+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Subtotal:${{ number_format($o->sub_total,$o->currency()->rounding) }}
 Tax (GST 10%)${{ number_format($o->tax_total,$o->currency()->rounding) }}
 Other Charges:$0.00
Total:${{ number_format($o->total,$o->currency()->rounding) }}
 Payments:${{ number_format($o->paid,$o->currency()->rounding) }}
Account Due:${{ number_format($o->due,$o->currency()->rounding) }}
+
+
+ +
+ + + +
+
+ Print + + +
+
+
+ + +
+@endsection + +@section('page-scripts') + + +@append \ No newline at end of file diff --git a/resources/theme/backend/adminlte/widgets/services_active.blade.php b/resources/theme/backend/adminlte/widgets/services_active.blade.php index 2463377..6301621 100644 --- a/resources/theme/backend/adminlte/widgets/services_active.blade.php +++ b/resources/theme/backend/adminlte/widgets/services_active.blade.php @@ -64,7 +64,7 @@ columns: [ { data: "service_id_url" }, { data: "category" }, - { data: "service_name" }, + { data: "name" }, { data: "product_name" }, { data: "status" }, { data: "next_invoice" } diff --git a/routes/web.php b/routes/web.php index 7c1f0b5..cffc3fd 100644 --- a/routes/web.php +++ b/routes/web.php @@ -22,22 +22,24 @@ Route::group(['middleware'=>['theme:adminlte-be','auth','role:wholesaler'],'pref Route::get('setup','AdminHomeController@setup'); Route::post('setup','AdminHomeController@setup_update'); Route::get('switch/start/{id}','\Leenooks\Controllers\AdminController@user_switch_start')->name('switch.user.stop'); - Route::get('switch/stop','\Leenooks\Controllers\AdminController@user_switch_stop')->name('switch.user.start'); Route::get('accounting/connect', 'AccountingController@connect'); }); +Route::get('admin/switch/stop','\Leenooks\Controllers\AdminController@user_switch_stop')->name('switch.user.start')->middleware('auth'); + // Our Reseller Routes Route::group(['middleware'=>['theme:adminlte-be','auth','role:reseller'],'prefix'=>'r'], function() { Route::get('supplier/index', 'SuppliersController@index'); Route::get('supplier/create', 'SuppliersController@create'); Route::post('supplier/store', 'SuppliersController@store'); - Route::get('home', 'UserHomeController@home'); + Route::get('home/{o}', 'UserHomeController@user'); }); // Our User Routes Route::group(['middleware'=>['theme:adminlte-be','auth'],'prefix'=>'u'], function() { Route::get('home', 'UserHomeController@home'); + Route::get('invoice/{o}', 'UserHomeController@invoice'); }); // Frontend Routes (Non-Authed Users)