400 lines
10 KiB
PHP
400 lines
10 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Carbon\Carbon;
|
|
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 */
|
|
|
|
/**
|
|
* A list of invoices that are in credit for all accounts
|
|
*
|
|
* @param Collection|NULL $invoices
|
|
* @return Collection
|
|
*/
|
|
public static function InvoicesCredit(Collection $invoices=NULL): Collection
|
|
{
|
|
return (new self)
|
|
->invoiceSummaryCredit($invoices,TRUE)
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* A list of invoices that are due for all accounts
|
|
*
|
|
* @param Collection|NULL $invoices
|
|
* @return Collection
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* Group this account is assigned to
|
|
* Groups are used for pricing control
|
|
*
|
|
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
|
|
*/
|
|
public function group()
|
|
{
|
|
return $this->hasOneThrough(Group::class,AccountGroup::class,'account_id','id','id','group_id');
|
|
}
|
|
|
|
/**
|
|
* Invoices created for this account
|
|
*/
|
|
public function invoices()
|
|
{
|
|
return $this->hasMany(Invoice::class)
|
|
->with(['items.taxes','payment_items.payment']);
|
|
}
|
|
|
|
/**
|
|
* Relation to only return active invoices
|
|
*/
|
|
public function invoices_active()
|
|
{
|
|
return $this->invoices()
|
|
->with('active',TRUE);;
|
|
}
|
|
|
|
/**
|
|
* Payments received and assigned to this account
|
|
*/
|
|
public function payments()
|
|
{
|
|
return $this->hasMany(Payment::class)
|
|
->with(['items']);
|
|
}
|
|
|
|
/**
|
|
* Relation to only return active payments
|
|
*/
|
|
public function payments_active()
|
|
{
|
|
return $this->payments()
|
|
->with('active',TRUE);
|
|
}
|
|
|
|
/**
|
|
* Return the link to a provider's info for this account
|
|
*/
|
|
public function providers()
|
|
{
|
|
return $this->belongsToMany(ProviderOauth::class,'account__provider')
|
|
->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()
|
|
->ServiceActive();
|
|
}
|
|
|
|
/**
|
|
* Supplier configuration for this account
|
|
*/
|
|
public function suppliers()
|
|
{
|
|
return $this->belongsToMany(Supplier::class)
|
|
->withPivot('supplier_ref','created_at');
|
|
}
|
|
|
|
/**
|
|
* Taxes applicable for this account
|
|
*/
|
|
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','ilike','%'.$term.'%')
|
|
->orWhere('address1','ilike','%'.$term.'%')
|
|
->orWhere('address2','ilike','%'.$term.'%')
|
|
->orWhere('city','ilike','%'.$term.'%');
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
/* ATTRIBUTES */
|
|
|
|
/**
|
|
* Get the address for the account
|
|
*
|
|
* @return Collection
|
|
*/
|
|
public function getAddressAttribute(): Collection
|
|
{
|
|
return collect([
|
|
'address1' => $this->address1,
|
|
'address2' => $this->address2,
|
|
'location' => sprintf('%s %s %s',
|
|
$this->city.(($this->state || $this->zip) ? ',' : ''),
|
|
$this->state,
|
|
$this->zip)
|
|
])
|
|
->filter();
|
|
}
|
|
|
|
/**
|
|
* Return the account name
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getNameAttribute(): string
|
|
{
|
|
return $this->company ?: ($this->user_id ? $this->user->getNameSurFirstAttribute() : '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 */
|
|
|
|
public function invoice_next(Carbon $date=NULL): Collection
|
|
{
|
|
$svs = $this
|
|
->services_active
|
|
->filter(fn($item)=>$item->isBilled() && $item->invoice_next)
|
|
->sortBy(fn($item)=>(string)$item->invoice_next);
|
|
|
|
// Collect all the invoice items for our active services
|
|
$nextdate = $date ?: $svs
|
|
->first()
|
|
?->invoice_next
|
|
->clone();
|
|
|
|
// Add any additional items that will be invoiced 30 days
|
|
$nextitemsdate = max($nextdate,Carbon::now())
|
|
->clone()
|
|
->addMonth()
|
|
->subDay()
|
|
->endOfday();
|
|
|
|
$items = $svs
|
|
->filter(fn($item)=>$item->invoice_next->lessThan($nextitemsdate))
|
|
->sortBy(fn($item)=>$item->invoice_next.$item->name)
|
|
->map(fn($item)=>$item->next_invoice_items($nextitemsdate))
|
|
->flatten();
|
|
|
|
// Add any account charges (charges with no active service)
|
|
foreach ($this->charges->filter(function($item) { return $item->unprocessed && ((! $this->service_id) || (! $item->service->isBilled())); }) as $oo) {
|
|
$ii = new InvoiceItem;
|
|
|
|
$ii->active = TRUE;
|
|
$ii->service_id = $oo->service_id;
|
|
$ii->product_id = $this->product_id;
|
|
$ii->quantity = $oo->quantity;
|
|
$ii->item_type = $oo->type;
|
|
$ii->price_base = $oo->amount;
|
|
$ii->start_at = $oo->start_at;
|
|
$ii->stop_at = $oo->stop_at;
|
|
$ii->module_id = 30; // @todo This shouldnt be hard coded
|
|
$ii->module_ref = $oo->id;
|
|
$ii->site_id = 1; // @todo
|
|
|
|
$ii->addTaxes($this->country->taxes);
|
|
$items->push($ii);
|
|
}
|
|
|
|
return $items;
|
|
}
|
|
|
|
/**
|
|
* List of invoices (summary) for this account
|
|
*
|
|
* @param Collection|NULL $invoices
|
|
* @param bool $all
|
|
* @return Builder
|
|
*/
|
|
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(COALESCE(amount,0)) 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-COALESCE(invoice_items.discount_amt,0)) AS NUMERIC),2))+SUM(ROUND(CAST(COALESCE(amount,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(fn($query)=>$query->where('invoice_item_taxes.active',TRUE)->orWhereNull('invoice_item_taxes.active'))
|
|
->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);
|
|
}
|
|
} |