'array', ]; protected $appends = [ 'account_name', 'admin_service_id_url', 'billing_price', 'name_short', 'next_invoice', 'product_category', 'product_name', 'service_id', 'service_id_url', 'status', ]; protected $visible = [ 'account_name', 'admin_service_id_url', 'active', 'billing_price', 'data_orig', 'id', 'name_short', 'next_invoice', 'product_category', 'product_name', 'service_id', 'service_id_url', 'status', ]; private $inactive_status = [ 'CANCELLED', 'ORDER-REJECTED', 'ORDER-CANCELLED', ]; /** * Valid status shows the applicable next status for an action on a service * Each status can be * + Approved, to proceed to the next valid status' * + Held, to a holding pattern status * + Rejected, reverted to an different status * + Cancel, to progress down a decomission route * + Updated, stay on the current status with new information * * @var array */ private $valid_status = [ // Order Submitted 'ORDER-SUBMIT' => ['approve'=>'ORDER-SENT','hold'=>'ORDER-HOLD','reject'=>'ORDER-REJECTED','cancel'=>'ORDER-CANCELLED'], // Order On Hold (Reason) 'ORDER-HOLD' => ['release'=>'ORDER-SUBMIT','update_reference'=>'ORDER-SENT'], // Order Rejected (Reason) 'ORDER-REJECTED' => [], // Order Cancelled 'ORDER-CANCELLED' => [], // Order Sent to Supplier 'ORDER-SENT' => ['update_reference'=>'ORDER-SENT','confirm'=>'ORDERED'], // Order Confirmed by Supplier 'ORDERED' => ['update_reference'=>'ORDER-SENT'], ]; /** * Account the service belongs to * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function account() { return $this->belongsTo(Account::class); } /** * Return Charges associated with this Service * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function charges() { return $this->hasMany(Charge::class) ->where('active','=',TRUE) ->orderBy('date_orig'); } // @todo changed to invoiced_items public function invoice_items($active=TRUE) { $query = $this->hasMany(InvoiceItem::class) ->where('item_type','=',0) ->orderBy('date_orig'); if ($active) $query->where('active','=',TRUE); return $query; } /** * Invoices for this service * */ public function invoices($active=TRUE) { $query = $this->hasManyThrough(Invoice::class,InvoiceItem::class,NULL,'id',NULL,'invoice_id') ->distinct('id') ->where('ab_invoice.site_id','=',$this->site_id) ->where('ab_invoice_item.site_id','=',$this->site_id) ->orderBy('date_orig') ->orderBy('due_date'); if ($active) $query->where('ab_invoice_item.active','=',TRUE) ->where('ab_invoice.active','=',TRUE); return $query; } /** * Account that ordered the service * * @todo changed to orderedby * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function orderby() { return $this->belongsTo(Account::class); } /** * Product of the service * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function product() { return $this->belongsTo(Product::class); } /** * Tenant that the service belongs to * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function site() { return $this->belongsTo(Site::class); } /** * Return a child model with details of the service * * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ public function type() { return $this->morphTo(null,'model','id','service_id'); } /** SCOPES **/ /** * Only query active categories */ public function scopeActive($query) { return $query->where(function () use ($query) { $query->where('active',TRUE)->orWhereNotIn('order_status',$this->inactive_status); }); } /** * Find inactive services. * * @param $query * @return mixed */ public function scopeInActive($query) { return $query->where(function () use ($query) { $query->where('active',FALSE)->orWhereIn('order_status',$this->inactive_status); }); } /** * Enable to perform queries without eager loading * * @param $query * @return mixed */ public function scopeNoEagerLoads($query){ return $query->setEagerLoads([]); } /** * Search for a record * * @param $query * @param string $term * @return */ public function scopeSearch($query,string $term) { return $query->where('id','like','%'.$term.'%'); } /** ATTRIBUTES **/ /** * Name of the account for this service * * @return mixed */ public function getAccountNameAttribute(): string { return $this->account->name; } /** * @deprecated Use getUrlAdminAttribute() */ public function getAdminServiceIdUrlAttribute() { return $this->getUrlAdminAttribute(); } public function getBillingPriceAttribute(): float { // @todo Temporary for services that dont have recur_schedule set. if (is_null($this->recur_schedule) OR is_null($this->product->price($this->recur_schedule))) $this->price=0; return $this->addTax(is_null($this->price) ? $this->product->price($this->recur_schedule) : $this->price); } /** * Return the service billing period * * @return string */ public function getBillingPeriodAttribute(): string { return Arr::get($this->product->PricePeriods(),$this->recur_schedule,'Unknown'); } /** * Date the service expires, also represents when it is paid up to * * @return string */ public function getExpiresAttribute(): string { return 'TBA'; } /** * Return the date for the next invoice * * @todo This function negates the need for date_next_invoice * @return null */ public function getInvoiceNextAttribute() { $last = $this->getInvoiceToAttribute(); $date = $last ? $last->addDay() : now(); return request()->wantsJson() ? $date->format('Y-m-d') : $date; } public function getInvoiceNextEndAttribute() { switch ($this->recur_schedule) { // Weekly case 0: $date = $this->getInvoiceNextAttribute()->addWeek(); break; // Monthly case 1: $date = $this->getInvoiceNextAttribute()->addMonth(); break; // Quarterly case 2: $date = $this->getInvoiceNextAttribute()->addQuarter(); break; // Half Yearly case 3: $date = $this->getInvoiceNextAttribute()->addQuarter(2); break; // Yearly case 4: $date = $this->getInvoiceNextAttribute()->addYear(); break; // Two Yearly case 5: $date = $this->getInvoiceNextAttribute()->addYear(2); break; // Three Yearly case 6: $date = $this->getInvoiceNextAttribute()->addYear(3); break; default: throw new \Exception('Unknown recur_schedule'); } return $date->subDay(); } /** * Get the date that the service has been invoiced to */ public function getInvoiceToAttribute() { return $this->invoice_items->count() ? $this->invoice_items->last()->date_stop : NULL; } public function getNameAttribute(): string { return $this->product->name_short.': '.$this->getNameShortAttribute(); } /** * Return the short name for the service. * * EG: * For ADSL, this would be the phone number, * For Hosting, this would be the domain name, etc */ public function getNameShortAttribute() { return $this->model ? $this->type->name : 'NAME UNKNOWN'; } /** * @deprecated see getInvoiceNextAttribute() */ public function getNextInvoiceAttribute() { return $this->getInvoiceNextAttribute(); } /** * This function will present the Order Info Details */ public function getOrderInfoDetailsAttribute(): string { if (! $this->order_info) return ''; $result = ''; foreach ($this->order_info as $k=>$v) { if (in_array($k,['order_reference'])) continue; $result .= sprintf('%s: %s
',ucfirst($k),$v); } return $result; } /** * Get the Product's Category for this service * */ public function getProductCategoryAttribute(): string { return $this->product->category; } /** * Get the Product's Short Name for the service * * @return string */ public function getProductNameAttribute(): string { return $this->product->name($this->account->language); } /** * @deprecated see getServiceIdAttribute() */ public function getServiceIdAttribute() { return $this->getSIDAttribute(); } /** * @deprecated see getUrlUserAttribute() */ public function getServiceIdUrlAttribute() { return $this->getUrlUserAttribute(); } /** * @deprecated see getServiceIdAttribute() */ public function getServiceNumberAttribute() { return $this->getSIDAttribute(); } /** * Services Unique Identifier * * @return string */ public function getSIDAttribute(): string { return sprintf('%02s-%04s.%05s',$this->site_id,$this->account_id,$this->id); } /** * Return the Service Status * * @return string */ public function getStatusAttribute(): string { if (! $this->order_status) return $this->active ? 'ACTIVE' : 'INACTIVE'; else return $this->order_status; } /** * Return the detailed order Status, with order reference numbers. * * @return string */ public function getStatusDetailAttribute(): string { return in_array($this->order_status,['ORDER-SENT','ORDER-HOLD','ORDERED']) ? sprintf('%s: #%s',$this->order_status,Arr::get($this->order_info,'order_reference','Unknown')) : ''; } /** * Return a HTML status box * * @return string */ public function getStatusHTMLAttribute(): string { $class = NULL; switch ($this->status) { case 'ACTIVE': $class = 'badge-success'; break; } return sprintf('%s',$class,$this->status); } /** * URL used by an admin to administer the record * * @return string */ public function getUrlAdminAttribute(): string { return sprintf('%s',$this->id,$this->service_id); } /** * URL used by an user to see the record * * @return string */ public function getUrlUserAttribute(): string { return sprintf('%s',$this->id,$this->service_id); } /** SETTERS **/ public function setDateOrigAttribute($value) { $this->attributes['date_orig'] = $value->timestamp; } public function setDateLastAttribute($value) { $this->attributes['date_last'] = $value->timestamp; } /** FUNCTIONS **/ /** * Add applicable tax to the cost * * @todo This needs to be calculated, not fixed at 1.1 * @param float $value * @return float */ private function addTax(float $value): float { return round($value*1.1,2); } public function invoices_due(): Collection { $this->load('invoice_items.invoice'); return $this->invoice_items->filter(function($item) { return $item->invoice->due > 0; }); } /** * Determine if a service is active. It is active, if active=1, or the order_status is not in inactive_status[] * * @return bool * @todo Remove active and have order_status reflect whether active or not */ public function isActive(): bool { return $this->active OR ($this->order_status AND ! in_array($this->order_status,$this->inactive_status)); } /** * Should this service be invoiced soon * * @todo get the number of days from account setup * @return bool */ public function isInvoiceDueSoon($days=30): bool { return (! $this->external_billing) AND (! $this->suspend_billing) AND $this->getInvoiceNextAttribute()->lessThan(now()->addDays($days)); } public function next_invoice_items(): \Illuminate\Support\Collection { $result = collect(); $o = new InvoiceItem; $o->active = TRUE; $o->service_id = $this->id; $o->product_id = $this->product_id; $o->quantity = 1; $o->item_type = 0; $o->price_base = $this->price ?: $this->product->price($this->recur_schedule); // @todo change to a method in this class $o->recurring_schedule = $this->recur_schedule; $o->date_start = $this->invoice_next; $o->date_stop = $this->invoice_next_end; $o->addTaxes(); $result->push($o); foreach ($this->charges->filter(function($item) { return ! $item->processed; }) as $oo) { $o = new InvoiceItem; $o->active = TRUE; $o->service_id = $oo->service_id; $o->product_id = $this->product_id; $o->quantity = $oo->quantity; $o->item_type = $oo->type; $o->price_base = $oo->amount; $o->date_start = $oo->date_charge; $o->date_stop = $oo->date_charge; $o->module_id = 30; // @todo This shouldnt be hard coded $o->module_ref = $oo->id; $o->addTaxes(); $result->push($o); } return $result; } /** * @todo * @param string $status * @return $this */ public function nextStatus(string $status) { if ($x=$this->validStatus($status)) { $this->order_status = $x; $this->save(); return $this; } abort(500,'Next Status not set up for:'.$this->order_status); } /** * This function will return the associated service model for the product type * @deprecated use $this->type */ private function ServicePlugin() { // @todo: All services should be linked to a product. This might require data cleaning for old services not linked to a product. if (! is_object($this->product)) return NULL; switch ($this->product->prod_plugin_file) { case 'ADSL': return $this->service_adsl; case 'DOMAIN': return $this->service_domain; case 'HOST': return $this->service_host; case 'SSL': return $this->service_ssl; case 'VOIP': return $this->service_voip; default: return NULL; } } /** * Return if the proposed status is valid. * * @param string $status * @return string | NULL */ private function testNextStatusValid(string $status) { return Arr::get(Arr::get($this->valid_status,$this->order_status,[]),$status,NULL); } /** * @deprecated use testNextStatusValid() */ public function validStatus(string $status) { return $this->testNextStatusValid($status); } }