From f561139d45816c5cf2a96581747dd5a901630e6e Mon Sep 17 00:00:00 2001 From: Deon George Date: Tue, 9 Jul 2024 20:17:30 +1000 Subject: [PATCH] Optimise Invoice --- app/Models/Account.php | 2 +- app/Models/Country.php | 8 - app/Models/Currency.php | 29 --- app/Models/Invoice.php | 204 +++++++++--------- app/Models/InvoiceItem.php | 2 +- app/Models/Site.php | 5 - config/osb.php | 1 + resources/views/email/user/invoice.blade.php | 4 +- .../a/payment/widgets/invoices.blade.php | 6 +- .../account/widget/service_active.blade.php | 6 +- .../backend/adminlte/invoice/view.blade.php | 64 +++--- 11 files changed, 146 insertions(+), 185 deletions(-) delete mode 100644 app/Models/Currency.php diff --git a/app/Models/Account.php b/app/Models/Account.php index df71a29..a233d68 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -102,7 +102,7 @@ class Account extends Model implements IDs public function invoices() { return $this->hasMany(Invoice::class) - ->with(['items.taxes','paymentitems.payment']); + ->with(['items.taxes','payment_items.payment']); } /** diff --git a/app/Models/Country.php b/app/Models/Country.php index 781e823..155cac7 100644 --- a/app/Models/Country.php +++ b/app/Models/Country.php @@ -10,14 +10,6 @@ class Country extends Model /* RELATIONS */ - /** - * The currency this country belongs to - */ - public function currency() - { - return $this->belongsTo(Currency::class); - } - public function taxes() { return $this->hasMany(Tax::class); diff --git a/app/Models/Currency.php b/app/Models/Currency.php deleted file mode 100644 index 22195b4..0000000 --- a/app/Models/Currency.php +++ /dev/null @@ -1,29 +0,0 @@ -hasOne(Country::class); - } - - /* METHODS */ - - public function round($value,$mode=self::ROUND_HALF_UP) - { - return round($value,$this->rounding,$mode); - } -} \ No newline at end of file diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 560b522..323f71a 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -7,6 +7,7 @@ use Clarkeash\Doorman\Facades\Doorman; use Clarkeash\Doorman\Models\Invite; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; +use Illuminate\Support\Collection; use Leenooks\Casts\LeenooksCarbon; use Leenooks\Traits\ScopeActive; @@ -19,16 +20,16 @@ use App\Traits\PushNew; * Invoices that belong to an Account * * Attributes for services: + * + created_at : Date the invoice was created * + due : Balance due on an invoice - * + due_date : Date the invoice is due - * + invoice_date : Date the invoice was created + * + due_at : Date the invoice is due * + lid : Local ID for invoice * + paid : Total of payments received (excluding pending) * + paid_date : Date the invoice was paid in full * + paid_pending : Total of pending payments received * + sid : System ID for invoice * + sub_total : Invoice sub-total before taxes - * + total_tax : Invoices total of taxes + * + tax_total : Invoices total of taxes * + total : Invoice total * * @package App\Models @@ -97,13 +98,13 @@ class Invoice extends Model implements IDs // Array of items that can be updated with PushNew protected $pushable = ['items']; - /* protected $with = [ - 'account.country.currency', - 'items.taxes', - 'paymentitems' + 'items_active:id,start_at,stop_at,quantity,price_base,discount_amt,item_type,product_id,service_id,invoice_id', + 'items_active.taxes:id,invoice_item_id,amount,tax_id', + 'items_active.product:id', + 'items_active.product.translate:id,product_id,name_short,name_detail', + 'payment_items_active:id,amount,payment_id,invoice_id', ]; - */ /* STATIC METHODS */ @@ -181,29 +182,58 @@ class Invoice extends Model implements IDs /* RELATIONS */ + /** + * Account this invoice belongs to + */ public function account() { return $this->belongsTo(Account::class); } + /** + * Items on this invoice belongs to + */ public function items() { return $this->hasMany(InvoiceItem::class) - ->where('active',TRUE) ->with(['taxes','product']); } + /** + * Active items on this invoice belongs to + */ + public function items_active() + { + return $this->items() + ->where('active',TRUE); + } + + /** + * Payments applied to this invoice + */ public function payments() { return $this->hasManyThrough(Payment::class,PaymentItem::class,NULL,'id',NULL,'payment_id') - ->active(); + ->where('active',TRUE); } - public function paymentitems() + /** + * Payment items attached to this invoice + */ + public function payment_items() { return $this->hasMany(PaymentItem::class); } + public function payment_items_active() + { + return $this->payment_items() + ->where('payment_items.active',TRUE); + } + + /** + * 3rd party provider details to this invoice (eg: accounting providers) + */ public function providers() { return $this->belongsToMany(ProviderOauth::class,'invoice__provider') @@ -236,31 +266,6 @@ class Invoice extends Model implements IDs return sprintf('%3.2f',$this->getTotalAttribute()-$this->getPaidAttribute()); } - /** - * @return mixed - * @todo Change references to due_at to use due_date - */ - public function getDueDateAttribute(): Carbon - { - return $this->due_at; - } - - /** - * Date the invoices was created - * - * @return Carbon - */ - public function getInvoiceDateAttribute(): Carbon - { - return $this->created_at; - } - - // @todo Move this to a site configuration - public function getInvoiceTextAttribute() - { - return sprintf('Thank you for using %s for your Internet Services.',config('site')->site_name); - } - /** * Total of payments received for this invoice * excluding pending payments @@ -269,9 +274,7 @@ class Invoice extends Model implements IDs */ public function getPaidAttribute(): float { - return $this->paymentitems - ->filter(function($item) { return ! $item->payment->pending_status && $item->payment->active; }) - ->sum('amount'); + return $this->payment_items_active->sum('amount'); } /** @@ -282,11 +285,13 @@ class Invoice extends Model implements IDs */ public function getPaidDateAttribute(): ?Carbon { + // If the invoice still has a due balance, its not paid if ($this->getDueAttribute()) return NULL; - $o = $this->payments - ->filter(function($item) { return ! $item->pending_status; }) + $o = $this + ->payments + ->filter(fn($item)=>(! $item->pending_status)) ->last(); return $o?->paid_at; @@ -299,8 +304,8 @@ class Invoice extends Model implements IDs */ public function getPaidPendingAttribute(): float { - return $this->paymentitems - ->filter(function($item) { return $item->payment->pending_status; }) + return $this->payment_items + ->filter(fn($item)=>$item->payment->pending_status) ->sum('amount'); } @@ -311,28 +316,17 @@ class Invoice extends Model implements IDs */ public function getSubTotalAttribute(): float { - return $this->items->where('active',TRUE)->sum('sub_total'); + return $this->items_active->sum('sub_total'); } /** * Get the invoices taxes total * * @return float - * @deprecated use getTotalTaxAttribute(); */ public function getTaxTotalAttribute(): float { - return $this->getTotalTaxAttribute(); - } - - /** - * Get the invoices taxes total - * - * @return float - */ - public function getTotalTaxAttribute(): float - { - return $this->items->where('active',TRUE)->sum('tax'); + return $this->items_active->sum('tax'); } /** @@ -342,17 +336,11 @@ class Invoice extends Model implements IDs */ public function getTotalAttribute(): float { - return $this->getSubTotalAttribute()+$this->getTotalTaxAttribute(); + return $this->getSubTotalAttribute()+$this->getTaxTotalAttribute(); } /* METHODS */ - // @todo This shouldnt be here - current should be handled at an account level. - public function currency() - { - return $this->account->country->currency; - } - /** * Return a download link for non-auth downloads * @@ -366,57 +354,37 @@ class Invoice extends Model implements IDs $tokendate = ($x=Carbon::now()->addDays(21)) > ($y=$this->due_at->addDays(21)) ? $x : $y; // Extend the expire date - if ($io AND ($tokendate > $io->valid_until)) { + if ($io && ($tokendate > $io->valid_until)) { $io->valid_until = $tokendate; $io->save(); } - $code = (! $io) ? Doorman::generate()->for($this->account->user->email)->uses(0)->expiresOn($tokendate)->make()->first()->code : $io->code; + $code = (! $io) + ? Doorman::generate() + ->for($this->account->user->email) + ->uses(0) + ->expiresOn($tokendate) + ->make() + ->first() + ->code + : $io->code; return url('u/invoice',[$this->id,'email',$code]); } - // @todo document - public function products() + /** + * Return all the items on an invoice for a particular service and product + * + * @param Product $po + * @param Service $so + * @return Collection + */ + public function product_service_items(Product $po,Service $so): Collection { - $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); - } - - return $return->sortBy(function ($item) { - return $item->name; - }); - } - - // @todo document - public function product_services(Product $po) - { - $return = collect(); - - $this->items->load(['service']); - - 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'); - } - - // @todo document - 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'); + return $this + ->items_active + ->filter(fn($item)=>($item->product_id === $po->id) && ($item->service_id === $so->id)) + ->sortBy('item_type'); } /** @@ -439,6 +407,7 @@ class Invoice extends Model implements IDs * * @param array $options * @return bool + * @todo Change this to a saving event */ public function save(array $options = []) { @@ -453,4 +422,29 @@ class Invoice extends Model implements IDs return parent::save($options); } + + /** + * Group the invoice items by product ID, returning the number of products and total + * + * @return Collection + */ + public function summary_products(): Collection + { + $return = collect(); + + foreach ($this->items_active->groupBy('product_id') as $o) { + $po = $o->first()->product; + $po->count = count($o->pluck('service_id')->unique()); + + $return->push([ + 'product' => $o->first()->product, + 'services' => $o->pluck('service_id')->unique(), + 'sub_total' => $o->sum('sub_total'), + 'tax_total' => $o->sum('tax'), + 'total' => $o->sum('total'), + ]); + } + + return $return->sortBy('product.name'); + } } \ No newline at end of file diff --git a/app/Models/InvoiceItem.php b/app/Models/InvoiceItem.php index ed3ff91..c1a9abb 100644 --- a/app/Models/InvoiceItem.php +++ b/app/Models/InvoiceItem.php @@ -125,7 +125,7 @@ class InvoiceItem extends Model */ public function getSubTotalAttribute(): float { - return sprintf('%3.2f',$this->quantity * $this->price_base - $this->discount_amt); + return sprintf('%3.2f',$this->quantity * ($this->price_base - $this->discount_amt)); } /** diff --git a/app/Models/Site.php b/app/Models/Site.php index 360e3ea..d46b7c1 100644 --- a/app/Models/Site.php +++ b/app/Models/Site.php @@ -44,11 +44,6 @@ class Site extends Model return $this->belongsTo(Country::class); } - public function currency() - { - return $this->belongsTo(Currency::class); - } - public function details() { return $this->hasMany(SiteDetail::class,NULL,'site_id'); diff --git a/config/osb.php b/config/osb.php index 5c2f2fd..56d8e5e 100644 --- a/config/osb.php +++ b/config/osb.php @@ -2,4 +2,5 @@ return [ 'language_id' => 1, + 'invoice_text' => 'Thank you for using our Internet Services.', ]; \ No newline at end of file diff --git a/resources/views/email/user/invoice.blade.php b/resources/views/email/user/invoice.blade.php index 8d1160b..5c78e0c 100644 --- a/resources/views/email/user/invoice.blade.php +++ b/resources/views/email/user/invoice.blade.php @@ -6,8 +6,8 @@ A new invoice has been generated on your account. A summary of that invoice is b @component('mail::table') | # | ID | Name | Amount | | -: | - |:-----| ------:| -@foreach ($invoice->products() as $po) -| {{ $po->count }} | {{ $po->product_id }} | {{ $po->name }} | ${{ number_format($invoice->items->filter(function($item) use ($po) {return $item->product_id == $po->id; })->sum('total'),$invoice->currency()->rounding) }} | +@foreach ($invoice->summary_products() as $item) +| {{ $item['services']->count() }} | {{ $item['product']->lid }} | {{ $item['product']->name }} | ${{ number_format($item['total'],2) }} | @endforeach ||| Sub Total | ${{ number_format($invoice->sub_total,2) }} | ||| Tax | ${{ number_format($invoice->tax_total,2) }} | diff --git a/resources/views/theme/backend/adminlte/a/payment/widgets/invoices.blade.php b/resources/views/theme/backend/adminlte/a/payment/widgets/invoices.blade.php index 00cbb09..9fccc01 100644 --- a/resources/views/theme/backend/adminlte/a/payment/widgets/invoices.blade.php +++ b/resources/views/theme/backend/adminlte/a/payment/widgets/invoices.blade.php @@ -3,7 +3,7 @@ @if(($x=$o->invoices() ->where('active',TRUE) ->orderBy('due_at') - ->with(['items.taxes','paymentitems.payment','account']) + ->with(['items.taxes','payment_items.payment','account']) ->get() ->filter(function($item) use ($pid) { return $item->due > 0 || $item->payments->search(function($item) use ($pid) { return $item->id == $pid; }) !== FALSE; }))->count()) @@ -22,12 +22,12 @@ @foreach ($x as $io) - + @endforeach diff --git a/resources/views/theme/backend/adminlte/account/widget/service_active.blade.php b/resources/views/theme/backend/adminlte/account/widget/service_active.blade.php index fd7e8e6..a1d1d19 100644 --- a/resources/views/theme/backend/adminlte/account/widget/service_active.blade.php +++ b/resources/views/theme/backend/adminlte/account/widget/service_active.blade.php @@ -1,4 +1,8 @@ +@php + $o->load(['services_active.invoiced_service_items_active_recent']); +@endphp +
@@ -6,7 +10,7 @@
- @if (($x=$o->services->where('active',TRUE))->count()) + @if (($x=$o->services_active)->count())
{{ $io->sid }}{{ $io->invoice_date->format('Y-m-d') }}{{ $io->created_at->format('Y-m-d') }} {{ $io->due_at->format('Y-m-d') }} {{ number_format($io->total,2) }} {{ number_format($io->due,2) }} - +
diff --git a/resources/views/theme/backend/adminlte/invoice/view.blade.php b/resources/views/theme/backend/adminlte/invoice/view.blade.php index 05f0a22..82e5270 100644 --- a/resources/views/theme/backend/adminlte/invoice/view.blade.php +++ b/resources/views/theme/backend/adminlte/invoice/view.blade.php @@ -1,3 +1,7 @@ +@php + use App\Models\{Checkout,Service}; +@endphp + @extends('adminlte::layouts.app') @@ -49,7 +53,7 @@ {!! $o->account->address->join('
') !!}

Email: {{ $o->account->user->email }}
- @if ($o->account->phone) + @if($o->account->phone) Phone: {{ $o->account->phone }}
@endif @@ -58,7 +62,7 @@
- + @@ -70,12 +74,12 @@ - + {{-- - + --}}
Issue Date:{{ $o->invoice_date->format('Y-m-d') }}Issue Date:{{ $o->created_at->format('Y-m-d') }}
Account:{{ $o->account->sid }}Payment Due:{{ $o->due_at->format('Y-m-d') }}
This Invoice Due:${{ number_format($o->total,$o->currency()->rounding) }}This Invoice Due:${{ number_format($o->total,2) }}
Total Account Due:${{ number_format($o->account->due,$o->currency()->rounding) }}Total Account Due:${{ number_format($o->account->due,2) }}
@@ -97,29 +101,29 @@ - @foreach ($o->products() as $po) + @foreach($o->summary_products() as $item) - {{ $po->count }} - #{{ $po->lid }} - {{ $po->name }} - ${{ number_format($o->items->filter(function($item) use ($po) {return $item->product_id == $po->id; })->sum('total'),$o->currency()->rounding) }} + {{ $item['services']->count() }} + #{{ $item['product']->lid }} + {{ $item['product']->name }} + ${{ number_format($item['total'],2) }} - @foreach ($o->product_services($po) as $so) - + @foreach(Service::whereIn('id',$item['services'])->get() as $so) +   Service: {{ $so->sid }}: [{{ $so->name }}]   - ${{ number_format($o->product_service_items($po,$so)->sum('total'),$o->currency()->rounding) }} + ${{ number_format($o->product_service_items($item['product'],$so)->sum('total'),2) }}   - @foreach ($o->product_service_items($po,$so) as $io) + @foreach($o->product_service_items($item['product'],$so) as $io)     {{ $io->item_type_name }} - ${{ number_format($io->total,$o->currency()->rounding) }} + ${{ number_format($io->total,2) }}   @endforeach @@ -141,18 +145,18 @@

Payment Methods:

- @foreach (\App\Models\Checkout::available() as $cho) - - - - - - - @endforeach + @foreach(Checkout::available() as $cho) + + + + + + + @endforeach
{{ $cho->name }}{{ $cho->description }}@includeIf('theme.backend.adminlte.payment.widget.plugin.'.strtolower($cho->plugin),['o'=>$cho])
{{ $cho->name }}{{ $cho->description }}@includeIf('theme.backend.adminlte.payment.widget.plugin.'.strtolower($cho->plugin),['o'=>$cho])

- {!! $o->invoice_text !!} + {{ config('osb.invoice_text') }}

@@ -161,12 +165,12 @@ - + - + @@ -176,23 +180,23 @@ - + @if($o->id) - + - - + + - + @endif
Subtotal:${{ number_format($o->sub_total,$o->currency()->rounding) }}${{ number_format($o->sub_total,2) }}
  Tax (GST 10%)${{ number_format($o->total_tax,$o->currency()->rounding) }}${{ number_format($o->tax_total,2) }}
 
Total:${{ number_format($o->total,$o->currency()->rounding) }}${{ number_format($o->total,2) }}
  Payments To Clear:${{ number_format($o->paid_pending,$o->currency()->rounding) }}${{ number_format($o->paid_pending,2) }}
  Payments:#{{ $o->payments->pluck('id')->join(', #') }}${{ number_format($o->paid,$o->currency()->rounding) }}#{{ $o->payment_items_active->pluck('payment_id')->join(', #') }}${{ number_format($o->paid,2) }}
Invoice Due:${{ number_format($o->due,$o->currency()->rounding) }}${{ number_format($o->due,2) }}