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(): Collection { // Collect all the invoice items for our active services $nextdate = ($x=$this ->services_active ->filter(fn($item)=>$item->isBilled() && $item->invoice_next) ->sortBy(fn($item)=>(string)$item->invoice_next)) ->first() ->invoice_next ->clone(); // Add any additional items that will be invoiced 30 days $nextitemsdate = max($nextdate,Carbon::now()) ->clone() ->addMonth() ->subDay() ->endOfday(); $items = $x ->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); } }