Code refactor work. New optimised query to get invoice status summary for an account

This commit is contained in:
Deon George 2024-07-05 12:04:08 +10:00
parent 7af09fd078
commit 2629b76ec3
15 changed files with 375 additions and 169 deletions

View File

@ -2,14 +2,13 @@
namespace App\Models; namespace App\Models;
use Awobaz\Compoships\Compoships;
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\DB;
use Leenooks\Traits\ScopeActive; use Leenooks\Traits\ScopeActive;
use App\Models\Scopes\SiteScope;
use App\Interfaces\IDs; use App\Interfaces\IDs;
use App\Traits\SiteID;
/** /**
* Class Account * Class Account
@ -20,12 +19,10 @@ use App\Traits\SiteID;
* + sid : System ID for account * + sid : System ID for account
* + name : Account Name * + name : Account Name
* + taxes : Taxes Applicable to this account * + taxes : Taxes Applicable to this account
*
* @package App\Models
*/ */
class Account extends Model implements IDs class Account extends Model implements IDs
{ {
use Compoships,HasFactory,ScopeActive,SiteID; use HasFactory,ScopeActive;
/* INTERFACES */ /* INTERFACES */
@ -41,13 +38,18 @@ class Account extends Model implements IDs
/* RELATIONS */ /* RELATIONS */
/**
* Charges assigned to this account
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function charges() public function charges()
{ {
return $this->hasMany(Charge::class); return $this->hasMany(Charge::class);
} }
/** /**
* Return the country the user belongs to * Country this account belongs to
*/ */
public function country() public function country()
{ {
@ -65,28 +67,47 @@ class Account extends Model implements IDs
} }
/** /**
* @return mixed * 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) * @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()
{ {
return $this->hasMany(Invoice::class) return $this->hasMany(Invoice::class)
->active()
->with(['items.taxes','paymentitems.payment']); ->with(['items.taxes','paymentitems.payment']);
} }
public function language() /**
* Relation to only return active invoices
*
* @todo Only return active invoice_items
*/
public function invoices_active()
{ {
return $this->belongsTo(Language::class); return $this->invoices()
->active();
} }
/**
* Payments received and assigned to this account
*/
public function payments() public function payments()
{ {
return $this->hasMany(Payment::class) return $this->hasMany(Payment::class)
->active()
->with(['items']); ->with(['items']);
} }
/**
* Relation to only return active payments
*
* @todo Only return active payment_items
*/
public function payments_active()
{
return $this->payments()
->active();
}
public function providers() public function providers()
{ {
return $this->belongsToMany(ProviderOauth::class,'account__provider') return $this->belongsToMany(ProviderOauth::class,'account__provider')
@ -94,25 +115,33 @@ class Account extends Model implements IDs
->withPivot('ref','synctoken','created_at','updated_at'); ->withPivot('ref','synctoken','created_at','updated_at');
} }
public function services($active=FALSE) /**
* Services assigned to this account
*/
public function services()
{ {
$query = $this->hasMany(Service::class,['account_id','site_id'],['id','site_id']) return $this->hasMany(Service::class)
->withoutGlobalScope(SiteScope::class)
->with(['product.translate','invoice_items']); ->with(['product.translate','invoice_items']);
return $active ? $query->active() : $query;
} }
public function site() /**
* Relation to only return active services
*/
public function services_active()
{ {
return $this->belongsTo(Site::class); return $this->services()
->active();
} }
public function taxes() public function taxes()
{ {
return $this->hasMany(Tax::class,'country_id','country_id'); return $this->hasMany(Tax::class,'country_id','country_id')
->select(['id','zone','rate','country_id']);
} }
/**
* User that owns this account
*/
public function user() public function user()
{ {
return $this->belongsTo(User::class); return $this->belongsTo(User::class);
@ -149,30 +178,27 @@ class Account extends Model implements IDs
* Get the address for the account * Get the address for the account
* *
* @return array * @return array
* @todo Change this to return a collection
*/ */
public function getAddressAttribute(): array public function getAddressAttribute(): array
{ {
return [ return collect([
$this->address1, 'address1' => $this->address1,
$this->address2, 'address2' => $this->address2,
sprintf('%s %s %s',$this->city.(($this->state OR $this->zip) ? ',' : ''),$this->state,$this->zip) 'location' => sprintf('%s %s %s',
]; $this->city.(($this->state || $this->zip) ? ',' : ''),
} $this->state,
$this->zip)
/** ])
* Account breadcrumb to render on pages ->filter()
* ->values()
* @return array ->toArray();
*/
public function getBreadcrumbAttribute(): array
{
return [$this->name => url('u/home',$this->user_id)];
} }
/** /**
* Return the account name * Return the account name
* *
* @return mixed|string * @return string
*/ */
public function getNameAttribute(): string public function getNameAttribute(): string
{ {
@ -184,7 +210,7 @@ class Account extends Model implements IDs
* *
* @return string * @return string
*/ */
public function getTypeAttribute() public function getTypeAttribute(): string
{ {
return $this->company ? 'Business' : 'Private'; return $this->company ? 'Business' : 'Private';
} }
@ -195,6 +221,7 @@ class Account extends Model implements IDs
* Get the due invoices on an account * Get the due invoices on an account
* *
* @return mixed * @return mixed
* @deprecated use invoiceSummary->filter(_balance > 0)
*/ */
public function dueInvoices() public function dueInvoices()
{ {
@ -203,6 +230,66 @@ class Account extends Model implements IDs
}); });
} }
/**
* List of invoices (summary) for this account
*
* @param Collection|NULL $invoices
* @return Collection
*/
public function invoiceSummary(Collection $invoices=NULL): Collection
{
return (new Invoice)
->select([
'invoice_id as id',
DB::raw('SUM(item) AS _item'),
DB::raw('SUM(tax) AS _tax'),
DB::raw('SUM(payments) AS _payment'),
DB::raw('SUM(discount) AS _discount'),
DB::raw('SUM(item_total) AS _item_total'),
DB::raw('SUM(payment_fees) AS _payment_fee'),
DB::raw('ROUND(CAST(SUM(item_total)-SUM(COALESCE(discount,0))+COALESCE(invoices.discount_amt,0) AS NUMERIC),2) AS _total'),
DB::raw('ROUND(CAST(SUM(item_total)-SUM(COALESCE(discount,0))+COALESCE(invoices.discount_amt,0)-SUM(payments) AS NUMERIC),2) AS _balance'),
'due_at',
])
->from(
(new Payment)
->select([
'invoice_id',
DB::raw('0 as item'),
DB::raw('0 as tax'),
DB::raw('0 as discount'),
DB::raw('0 as item_total'),
DB::raw('SUM(amount) AS payments'),
DB::raw('SUM(fees_amt) AS payment_fees'),
])
->join('payment_items',['payment_items.payment_id'=>'payments.id'])
->where('payments.active',TRUE)
->where('payment_items.active',TRUE)
->groupBy(['payment_items.invoice_id'])
->union(
(new InvoiceItem)
->select([
'invoice_id',
DB::raw('ROUND(CAST(SUM(quantity*price_base) AS NUMERIC),2) AS item'),
DB::raw('ROUND(CAST(SUM(amount) AS NUMERIC),2) AS tax'),
DB::raw('SUM(COALESCE(invoice_items.discount_amt,0)) AS discount'),
DB::raw('ROUND(CAST(SUM(ROUND(CAST(quantity*price_base AS NUMERIC),2))+SUM(ROUND(CAST(amount AS NUMERIC),2))-SUM(COALESCE(invoice_items.discount_amt,0)) AS NUMERIC),2) AS item_total'),
DB::raw('0 as payments'),
DB::raw('0 as payment_fees'),
])
->leftjoin('invoice_item_taxes',['invoice_item_taxes.invoice_item_id'=>'invoice_items.id'])
->rightjoin('invoices',['invoices.id'=>'invoice_items.invoice_id'])
->where('invoice_items.active',TRUE)
->where('invoices.active',TRUE)
->groupBy(['invoice_items.invoice_id']),
),'p')
->join('invoices',['invoices.id'=>'invoice_id'])
->where('account_id',$this->id)
->groupBy(['p.invoice_id'])
->groupBy(['due_at','discount_amt'])
->get();
}
/** /**
* Return the taxed value of a value * Return the taxed value of a value
* *

View File

@ -57,16 +57,18 @@ class Charge extends Model
/* SCOPES */ /* SCOPES */
/** @deprecated use pending */
public function scopeUnprocessed($query) public function scopeUnprocessed($query)
{ {
return $this->scopePending();
}
public function scopePending($query) {
return $query return $query
->where('active',TRUE) ->active()
->whereNotNull('charge_at') ->whereNotNull('charge_at')
->whereNotNull('type') ->whereNotNull('type')
->where(function($q) { ->where(fn($query)=>$query->where('processed',FALSE)->orWhereNull('processed'));
return $q->where('processed',FALSE)
->orWhereNull('processed');
});
} }
/* ATTRIBUTES */ /* ATTRIBUTES */

View File

@ -2,7 +2,6 @@
namespace App\Models; namespace App\Models;
use Awobaz\Compoships\Compoships;
use Carbon\Carbon; use Carbon\Carbon;
use Clarkeash\Doorman\Facades\Doorman; use Clarkeash\Doorman\Facades\Doorman;
use Clarkeash\Doorman\Models\Invite; use Clarkeash\Doorman\Models\Invite;
@ -11,7 +10,7 @@ use Illuminate\Support\Arr;
use Leenooks\Traits\ScopeActive; use Leenooks\Traits\ScopeActive;
use App\Interfaces\IDs; use App\Interfaces\IDs;
use App\Traits\{PushNew,SiteID}; use App\Traits\PushNew;
/** /**
* Class Invoice * Class Invoice
@ -34,14 +33,11 @@ use App\Traits\{PushNew,SiteID};
*/ */
class Invoice extends Model implements IDs class Invoice extends Model implements IDs
{ {
use Compoships,PushNew,ScopeActive,SiteID; use PushNew,ScopeActive;
protected $casts = [ protected $casts = [
'reminders'=>'json', 'reminders'=>'json',
]; 'due_at'=>'datetime:y-m-d',
protected $dates = [
'due_at',
]; ];
public const BILL_WEEKLY = 0; public const BILL_WEEKLY = 0;
@ -105,11 +101,6 @@ class Invoice extends Model implements IDs
]; ];
*/ */
// Caching variables
private int $_paid = 0;
private int $_total = 0;
private int $_total_tax = 0;
/* STATIC METHODS */ /* STATIC METHODS */
/** /**

View File

@ -2,7 +2,6 @@
namespace App\Models; namespace App\Models;
use Awobaz\Compoships\Compoships;
use Carbon\Carbon; use Carbon\Carbon;
use Exception; use Exception;
use Illuminate\Database\Eloquent\Casts\AsCollection; use Illuminate\Database\Eloquent\Casts\AsCollection;
@ -19,10 +18,8 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
use Leenooks\Carbon as LeenooksCarbon; use Leenooks\Carbon as LeenooksCarbon;
use App\Models\Product\Type; use App\Models\Product\Type;
use App\Models\Scopes\SiteScope;
use App\Interfaces\IDs; use App\Interfaces\IDs;
use App\Traits\ScopeServiceUserAuthorised; use App\Traits\ScopeServiceUserAuthorised;
use App\Traits\SiteID;
/** /**
* Class Service * Class Service
@ -60,24 +57,23 @@ use App\Traits\SiteID;
*/ */
class Service extends Model implements IDs class Service extends Model implements IDs
{ {
use HasFactory,ScopeServiceUserAuthorised,SiteID,Compoships; use HasFactory,ScopeServiceUserAuthorised;
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' => 'datetime:Y-m-d', // @todo Can these be removed, since we can work out invoice dynamically now
'stop_at' => 'datetime:Y-m-d',
'start_at' => 'datetime:Y-m-d',
]; ];
protected $dates = [ /** @deprecated */
'invoice_last_at',
'invoice_next_at',
'start_at',
'stop_at',
];
protected $appends = [ protected $appends = [
'category_name', 'category_name',
'name_short', 'name_short',
]; ];
/** @deprecated */
protected $visible = [ protected $visible = [
// 'account_name', // 'account_name',
// 'admin_service_id_url', // 'admin_service_id_url',
@ -95,8 +91,6 @@ class Service extends Model implements IDs
]; ];
protected $with = [ protected $with = [
//'invoice_items',
//'product.type.supplied',
'type', 'type',
]; ];
@ -332,8 +326,7 @@ class Service extends Model implements IDs
*/ */
public function account() public function account()
{ {
return $this->belongsTo(Account::class,['account_id','site_id'],['id','site_id']) return $this->belongsTo(Account::class);
->withoutGlobalScope(SiteScope::class);
} }
/** /**
@ -353,12 +346,31 @@ class Service extends Model implements IDs
*/ */
public function charges() public function charges()
{ {
return $this->hasMany(Charge::class,['service_id','site_id'],['id','site_id']) return $this->hasMany(Charge::class)
->withoutGlobalScope(SiteScope::class)
->where('active','=',TRUE)
->orderBy('created_at'); ->orderBy('created_at');
} }
/**
* Return only the active charges
*/
public function charges_active()
{
return $this->charges()
->active();
}
/**
* Return only the charges not yet processed
*/
public function charges_pending()
{
return $this->charges()
->pending();
}
/**
* Product changes for this service
*/
public function changes() public function changes()
{ {
return $this->belongsToMany(Product::class,'service__change','service_id','product_id','id','id') return $this->belongsToMany(Product::class,'service__change','service_id','product_id','id','id')
@ -367,53 +379,80 @@ class Service extends Model implements IDs
->withTimestamps(); ->withTimestamps();
} }
// @todo changed to invoiced_items /**
* @deprecated use invoiced_items
*/
public function invoice_items($active=TRUE) public function invoice_items($active=TRUE)
{ {
$query = $this->hasMany(InvoiceItem::class,['service_id','site_id'],['id','site_id']) return $this->invoiced_items_active();
->withoutGlobalScope(SiteScope::class)
->where('item_type','=',0)
->orderBy('start_at');
// @todo Change to $query->active();
if ($active)
$query->where('active','=',TRUE);
return $query;
} }
/** /**
* Invoices for this service * Invoices that this service is itemised on
*/ */
public function invoices($active=TRUE) public function invoiced_items()
{ {
$query = $this->hasManyThrough(Invoice::class,InvoiceItem::class,NULL,'id',NULL,'invoice_id') return $this->hasMany(InvoiceItem::class)
->when($this->site_id,function($q) { ->with(['taxes']);
return $q->where('invoices.site_id', $this->site_id)
->withoutGlobalScope(SiteScope::class);
})
->distinct('id')
->where('invoices.site_id','=',$this->site_id)
->where('invoice_items.site_id','=',$this->site_id)
->orderBy('created_at')
->orderBy('due_at');
if ($active)
$query->where('invoice_items.active','=',TRUE)
->where('invoices.active','=',TRUE);
return $query;
} }
/** /**
* Account that ordered the service * Invoices that this service is itemised on that is active
*/
public function invoiced_items_active()
{
return $this->invoiced_items()
->where('active',TRUE);
}
/**
* Return the extra charged items for this service (ie: item_type != 0)
*/
public function invoiced_extra_items()
{
return $this->hasMany(InvoiceItem::class)
->where('item_type','<>',0)
->orderBy('service_id')
->orderBy('start_at');
}
/**
* Return active extra items charged
*/
public function invoiced_extra_items_active()
{
return $this->invoiced_extra_items()
->where('active',TRUE);
}
/**
* Return the service charged items for this service (ie: item_type == 0)
*/
public function invoiced_service_items()
{
return $this->hasMany(InvoiceItem::class)
->where('item_type','=',0)
->orderBy('service_id')
->orderBy('start_at');
}
/**
* Return active service items charged
*/
public function invoiced_service_items_active()
{
return $this->invoiced_service_items()
->where('active',TRUE);
}
/**
* User that ordered the service
* *
* @return BelongsTo * @return BelongsTo
*/ */
public function orderedby() public function orderedby()
{ {
return $this->belongsTo(Account::class,['ordered_by','site_id'],['id','site_id']) return $this->belongsTo(User::class);
->withoutGlobalScope(SiteScope::class);
} }
/** /**
@ -423,8 +462,7 @@ class Service extends Model implements IDs
*/ */
public function product() public function product()
{ {
return $this->belongsTo(Product::class,['product_id','site_id'],['id','site_id']) return $this->belongsTo(Product::class);
->withoutGlobalScope(SiteScope::class);
} }
/** /**
@ -444,10 +482,11 @@ class Service extends Model implements IDs
*/ */
public function scopeActive($query) public function scopeActive($query)
{ {
return $query->where(function () use ($query) { return $query->where(
fn($query)=>
$query->where($this->getTable().'.active',TRUE) $query->where($this->getTable().'.active',TRUE)
->orWhereNotIn('order_status',self::INACTIVE_STATUS); ->orWhereNotIn('order_status',self::INACTIVE_STATUS)
}); );
} }
/** /**
@ -458,20 +497,11 @@ class Service extends Model implements IDs
*/ */
public function scopeInActive($query) public function scopeInActive($query)
{ {
return $query->where(function () use ($query) { return $query->where(
fn($query)=>
$query->where($this->getTable().'.active',FALSE) $query->where($this->getTable().'.active',FALSE)
->orWhereIn('order_status',self::INACTIVE_STATUS); ->orWhereIn('order_status',self::INACTIVE_STATUS)
}); );
}
/**
* Enable to perform queries without eager loading
*
* @param $query
* @return mixed
*/
public function scopeNoEagerLoads($query){
return $query->setEagerLoads([]);
} }
/** /**
@ -723,7 +753,7 @@ class Service extends Model implements IDs
if (! $this->product->price_recur_strict) if (! $this->product->price_recur_strict)
return 1; return 1;
$n = $this->invoice_next->diff($this->invoice_next_end)->days+1; $n = round($this->invoice_next->diffInDays($this->invoice_next_end),0);
switch ($this->recur_schedule) { switch ($this->recur_schedule) {
case Invoice::BILL_WEEKLY: case Invoice::BILL_WEEKLY:
@ -735,7 +765,7 @@ class Service extends Model implements IDs
break; break;
case Invoice::BILL_QUARTERLY: case Invoice::BILL_QUARTERLY:
$d = $this->invoice_next->addQuarter()->startOfQuarter()->diff($this->invoice_next_end->startOfQuarter())->days; $d = round($this->invoice_next_end->startOfQuarter()->diffInDays($this->invoice_next->addQuarter()->startOfQuarter()),1);
break; break;
case Invoice::BILL_SEMI_YEARLY: case Invoice::BILL_SEMI_YEARLY:
@ -845,19 +875,15 @@ class Service extends Model implements IDs
*/ */
public function getPaidToAttribute(): ?Carbon public function getPaidToAttribute(): ?Carbon
{ {
if (! $this->invoices->count()) // Last paid invoice
return NULL; $lastpaid = $this
->invoices()
->filter(fn($item)=>$item->_balance <= 0)
->last();
foreach ($this->invoices->reverse() as $o) return $lastpaid
if ($o->due == 0) ? $this->invoiced_service_items_active->where('invoice_id',$lastpaid->id)->where('type',0)->max('stop_at')
break; : NULL;
return $o->items
->filter(function($item) {
return $item->item_type === 0;
})
->last()
->stop_at;
} }
/** /**
@ -1148,6 +1174,15 @@ class Service extends Model implements IDs
return $this->product->hasUsage(); return $this->product->hasUsage();
} }
/**
* Return this service invoices
*/
public function invoices()
{
return $this->account
->invoiceSummary($this->invoiced_service_items_active->pluck('invoice_id'));
}
/** /**
* 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[]
* *

View File

@ -20,10 +20,6 @@ class Broadband extends Type implements ServiceUsage
{ {
private const LOGKEY = 'MSB'; private const LOGKEY = 'MSB';
protected $casts = [
'connect_at'=>'datetime:Y-m-d',
'expire_at'=>'datetime:Y-m-d',
];
protected $table = 'service_broadband'; protected $table = 'service_broadband';
/* INTERFACES */ /* INTERFACES */

View File

@ -14,7 +14,9 @@ class Domain extends Type
use ServiceDomains; use ServiceDomains;
protected $table = 'service_domain'; protected $table = 'service_domain';
protected $with = ['tld']; protected $with = [
'tld',
];
/* OVERRIDES */ /* OVERRIDES */

View File

@ -13,5 +13,7 @@ class Email extends Type
use ServiceDomains; use ServiceDomains;
protected $table = 'service_email'; protected $table = 'service_email';
protected $with = ['tld']; protected $with = [
'tld',
];
} }

View File

@ -14,7 +14,9 @@ class Host extends Type
use ServiceDomains; use ServiceDomains;
protected $table = 'service_host'; protected $table = 'service_host';
protected $with = ['tld']; protected $with = [
'tld',
];
/* RELATIONS */ /* RELATIONS */

View File

@ -8,10 +8,6 @@ namespace App\Models\Service;
*/ */
class Phone extends Type class Phone extends Type
{ {
protected $dates = [
'connect_at',
'expire_at',
];
protected $table = 'service_phone'; protected $table = 'service_phone';
/* INTERFACES */ /* INTERFACES */

View File

@ -14,9 +14,11 @@ abstract class Type extends Model implements ServiceItem
{ {
use ScopeServiceActive,ScopeServiceUserAuthorised; use ScopeServiceActive,ScopeServiceUserAuthorised;
protected $dates = [ protected $casts = [
'expire_at', 'connect_at' => 'datetime:Y-m-d',
'expire_at' => 'datetime:Y-m-d',
]; ];
public $timestamps = FALSE; public $timestamps = FALSE;
/* RELATIONS */ /* RELATIONS */

View File

@ -9,6 +9,7 @@
"ext-curl": "*", "ext-curl": "*",
"ext-pdo": "*", "ext-pdo": "*",
"barryvdh/laravel-snappy": "^1.0", "barryvdh/laravel-snappy": "^1.0",
"clarkeash/doorman": "^9.0",
"eduardokum/laravel-mail-auto-embed": "^2.0", "eduardokum/laravel-mail-auto-embed": "^2.0",
"laravel/dreamscape": "^0.1.0", "laravel/dreamscape": "^0.1.0",
"laravel/framework": "^11.0", "laravel/framework": "^11.0",

58
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "65656094f15929a06f03ee8719037bdd", "content-hash": "b84c28d616dc200d6583006761ae9e0d",
"packages": [ "packages": [
{ {
"name": "barryvdh/laravel-snappy", "name": "barryvdh/laravel-snappy",
@ -213,6 +213,62 @@
], ],
"time": "2024-02-09T16:56:22+00:00" "time": "2024-02-09T16:56:22+00:00"
}, },
{
"name": "clarkeash/doorman",
"version": "v9.0.0",
"source": {
"type": "git",
"url": "https://github.com/clarkeash/doorman.git",
"reference": "e94b6be6a0996b0fd457a4165c4a45016969ba5e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/clarkeash/doorman/zipball/e94b6be6a0996b0fd457a4165c4a45016969ba5e",
"reference": "e94b6be6a0996b0fd457a4165c4a45016969ba5e",
"shasum": ""
},
"require": {
"laravel/framework": "^11.0",
"php": "^8.2",
"ramsey/uuid": "^4.0"
},
"require-dev": {
"mockery/mockery": "^1.4",
"orchestra/testbench": "^9.0",
"phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Clarkeash\\Doorman\\Providers\\DoormanServiceProvider"
],
"aliases": {
"Doorman": "Clarkeash\\Doorman\\Facades\\Doorman"
}
}
},
"autoload": {
"psr-4": {
"Clarkeash\\Doorman\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ashley Clarke",
"email": "me@ashleyclarke.me"
}
],
"support": {
"issues": "https://github.com/clarkeash/doorman/issues",
"source": "https://github.com/clarkeash/doorman/tree/v9.0.0"
},
"time": "2024-03-25T20:55:39+00:00"
},
{ {
"name": "defuse/php-encryption", "name": "defuse/php-encryption",
"version": "v2.4.0", "version": "v2.4.0",

View File

@ -122,17 +122,3 @@
</div> </div>
</div> </div>
@endsection @endsection
@section('page-scripts')
<script>
$(document).ready(function() {
$('body')
.addClass('sidebar-collapse')
.delay(500)
.queue(function () {
window.dispatchEvent(new Event('resize'));
$(this).dequeue();
});
});
</script>
@append

View File

@ -41,8 +41,11 @@
</div> </div>
</div> </div>
@section('page-scripts') @section('page-styles')
@css(datatables,bootstrap4|rowgroup) @css(datatables,bootstrap4|rowgroup)
@append
@section('page-scripts')
@js(datatables,bootstrap4|rowgroup) @js(datatables,bootstrap4|rowgroup)
<script type="text/javascript"> <script type="text/javascript">

View File

@ -0,0 +1,45 @@
<table class="table table-sm" id="svc_bill_hist">
<thead>
<tr>
<th>Invoice</th>
<th>Start</th>
<th>Stop</th>
<th>Service</th>
<th>Extras</th>
<th class="text-right">Total</th>
</tr>
</thead>
<tbody>
@foreach($o->invoiced_items_active->groupBy('invoice_id') as $oo)
<tr>
<td><a href="{{ url('u/invoice',($x=$oo->first())->invoice_id) }}">{{ $x->invoice_id }}</a></td>
<td>{{ ($y=$oo->where('item_type',0))->min('start_at')->format('Y-m-d') }}</td>
<td>{{ $y->max('stop_at')->format('Y-m-d') }}</td>
<th>{{ number_format($y->sum('total'),2) }}</th>
<th>{{ number_format($oo->where('item_type','<>',0)->sum('total'),2) }}</th>
<td class="text-right">{{ number_format($oo->sum('total'),2) }}</td>
</tr>
@endforeach
</tbody>
</table>
@section('page-styles')
@css(datatables,bootstrap4)
@append
@section('page-scripts')
@js(datatables,bootstrap4)
<script type="text/javascript">
$(document).ready(function() {
$('#svc_bill_hist').DataTable({
language: {
emptyTable: "No Invoices"
},
order: [1,'desc'],
pageLength: 10
});
});
</script>
@append