osb/app/Models/Account.php

344 lines
8.6 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Leenooks\Traits\ScopeActive;
use App\Interfaces\IDs;
/**
* Class Account
* Service Accounts
*
* Attributes for accounts:
* + lid : Local ID for account
* + sid : System ID for account
* + name : Account Name
* + taxes : Taxes Applicable to this account
*/
class Account extends Model implements IDs
{
use HasFactory,ScopeActive;
/* STATIC */
public static function InvoicesCredit(Collection $invoices=NULL): Collection
{
return (new self)
->invoiceSummaryCredit($invoices,TRUE)
->get();
}
public static function InvoicesDue(Collection $invoices=NULL): Collection
{
return (new self)
->invoiceSummaryDue($invoices,TRUE)
->get();
}
/* INTERFACES */
public function getLIDAttribute(): string
{
return sprintf('%04s',$this->id);
}
public function getSIDAttribute(): string
{
return sprintf('%02s-%s',$this->site_id,$this->getLIDAttribute());
}
/* RELATIONS */
/**
* Charges assigned to this account
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function charges()
{
return $this->hasMany(Charge::class);
}
/**
* Country this account belongs to
*/
public function country()
{
return $this->belongsTo(Country::class);
}
public function external()
{
return $this->belongsToMany(External\Integrations::class,'external_account',NULL,'external_integration_id');
}
public function group()
{
return $this->hasOneThrough(Group::class,AccountGroup::class,'account_id','id','id','group_id');
}
/**
* 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()
{
return $this->hasMany(Invoice::class)
->with(['items.taxes','paymentitems.payment']);
}
/**
* Relation to only return active invoices
*
* @todo Only return active invoice_items
*/
public function invoices_active()
{
return $this->invoices()
->active();
}
/**
* Payments received and assigned to this account
*/
public function payments()
{
return $this->hasMany(Payment::class)
->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()
{
return $this->belongsToMany(ProviderOauth::class,'account__provider')
->where('account__provider.site_id',$this->site_id)
->withPivot('ref','synctoken','created_at','updated_at');
}
/**
* Services assigned to this account
*/
public function services()
{
return $this->hasMany(Service::class)
->with(['product.translate','product.type.supplied']);
}
/**
* Relation to only return active services
*/
public function services_active()
{
return $this->services()
->active();
}
public function taxes()
{
return $this->hasMany(Tax::class,'country_id','country_id')
->select(['id','zone','rate','country_id']);
}
/**
* User that owns this account
*/
public function user()
{
return $this->belongsTo(User::class);
}
/* SCOPES */
/**
* Search for a record
*
* @param $query
* @param string $term
* @return mixed
*/
public function scopeSearch($query,string $term)
{
// Build our where clause
if (is_numeric($term)) {
$query->where('id','like','%'.$term.'%');
} else {
$query->where('company','like','%'.$term.'%')
->orWhere('address1','like','%'.$term.'%')
->orWhere('address2','like','%'.$term.'%')
->orWhere('city','like','%'.$term.'%');
}
return $query;
}
/* ATTRIBUTES */
/**
* Get the address for the account
*
* @return array
* @todo Change this to return a collection
*/
public function getAddressAttribute(): array
{
return collect([
'address1' => $this->address1,
'address2' => $this->address2,
'location' => sprintf('%s %s %s',
$this->city.(($this->state || $this->zip) ? ',' : ''),
$this->state,
$this->zip)
])
->filter()
->values()
->toArray();
}
/**
* Return the account name
*
* @return string
*/
public function getNameAttribute(): string
{
return $this->company ?: ($this->user_id ? $this->user->getSurFirstNameAttribute() : 'LID:'.$this->id);
}
/**
* Return the type of account this is - if it has a company name, then its a business account.
*
* @return string
*/
public function getTypeAttribute(): string
{
return $this->company ? 'Business' : 'Private';
}
/* 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
*
* @param Collection|NULL $invoices
* @return Collection
*/
public function invoiceSummary(Collection $invoices=NULL,bool $all=FALSE): Builder
{
return (new Invoice)
->select([
'invoices.account_id',
'invoices.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)+COALESCE(invoices.discount_amt,0) 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)-COALESCE(invoices.discount_amt,0) AS NUMERIC),2) AS _total'),
DB::raw('ROUND(CAST(SUM(item_total)-COALESCE(invoices.discount_amt,0)-SUM(payments) AS NUMERIC),2) AS _balance'),
'invoices.due_at',
'invoices.created_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'),
])
->leftjoin('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('ROUND(CAST(SUM(COALESCE(invoice_items.discount_amt,0)) AS NUMERIC),2) AS discount'),
DB::raw('ROUND(CAST(SUM(ROUND(CAST(quantity*price_base AS NUMERIC),2))+SUM(ROUND(CAST(amount AS NUMERIC),2))-SUM(ROUND(CAST(COALESCE(invoice_items.discount_amt,0) AS NUMERIC),2)) 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('invoice_item_taxes.active',TRUE)
->where('invoices.active',TRUE)
->groupBy(['invoice_items.invoice_id']),
),'p')
->join('invoices',['invoices.id'=>'invoice_id'])
->when(($all === FALSE),fn($query)=>$query->where('invoices.account_id',$this->id))
->orderBy('due_at')
->groupBy(['invoices.account_id','invoices.id','invoices.created_at','invoices.due_at','invoices.discount_amt'])
->with(['account']);
}
public function invoiceSummaryDue(Collection $invoices=NULL,bool $all=FALSE): Builder
{
return $this->invoiceSummary($invoices,$all)
->havingRaw('ROUND(CAST(SUM(item_total)-COALESCE(invoices.discount_amt,0)-SUM(payments) AS NUMERIC),2) > 0');
}
public function invoiceSummaryCredit(Collection $invoices=NULL,bool $all=FALSE): Builder
{
return $this->invoiceSummary($invoices,$all)
->havingRaw('ROUND(CAST(SUM(item_total)-COALESCE(invoices.discount_amt,0)-SUM(payments) AS NUMERIC),2) < 0');
}
public function invoiceSummaryPast(Collection $invoices=NULL,bool $all=FALSE): Builder
{
return $this->invoiceSummary($invoices,$all)
->join('payment_items',['payment_items.invoice_id'=>'invoices.id'])
->join('payments',['payments.id'=>'payment_items.payment_id'])
->addSelect(DB::raw('max(paid_at) as _paid_at'))
->havingRaw('ROUND(CAST(SUM(item_total)-COALESCE(invoices.discount_amt,0)-SUM(payments) AS NUMERIC),2) <= 0');
}
/**
* Return the taxed value of a value
*
* @param float $value
* @return float
*/
public function taxed(float $value): float
{
return Tax::calc($value,$this->taxes);
}
}