'collection', ]; public $dateFormat = 'U'; protected $table = 'ab_service'; 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', ]; protected $with = [ 'account.language', 'charges', 'invoice_items', 'product', 'type', ]; 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 */ public static $action_progress = [ // Order Submitted 'ORDER-SUBMIT' => [ 'fail'=>FALSE, // Progress to next stages by who 'next'=>[ 'ORDER-ACCEPT'=>['customer'], 'SETUP-PAYMENT-WAIT'=>['reseller','wholesaler'] ], // Manual or System moves to the next stage 'system'=>TRUE, 'method'=>'action_order_submit', 'title'=>'Order Submit', ], // Client accepts order, if performed by RW 'ORDER-ACCEPT' => [ 'fail'=>FALSE, 'next'=>[ 'SETUP-PAYMENT-WAIT'=>['customer'], ], 'system'=>FALSE, 'method'=>'action_order_accept', 'title'=>'Client Accept Order', ], // If the product has a setup, collect payment information 'SETUP-PAYMENT-WAIT' => [ 'fail'=>FALSE, 'next'=>[ 'PAYMENT-WAIT'=>['customer'], ], 'system'=>FALSE, 'method'=>'action_setup_payment_wait', 'title'=>'Setup Payment', ], 'PAYMENT-WAIT' => [ 'fail'=>FALSE, 'next'=>[ 'PAYMENT-CHECK'=>['reseller','wholesaler'], ], 'system'=>FALSE, 'method'=>'action_payment_wait', 'title'=>'Service Payment', ], 'PAYMENT-CHECK' => [ 'fail'=>'ORDER-HOLD', 'next'=>[ 'ORDER-SENT'=>[], ], 'system'=>TRUE, 'method'=>'action_payment_check', 'title'=>'Validate Payment Method', ], // 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' => [ 'fail'=>'ORDER-HOLD', 'next'=>[ 'ORDERED'=>['wholesaler'], ], 'system'=>FALSE, 'method'=>'action_order_sent', 'title'=>'Send Order', ], // Order Confirmed by Supplier 'ORDERED' => [ 'fail'=>false, 'next'=>[ 'PROVISION-HOLD'=>['wholesaler'], 'PROVISION-PLANNED'=>['wholesaler'], 'PROVISIONED'=>['wholesaler'], ], 'system'=>FALSE, 'method'=>'action_ordered', 'title'=>'Service Ordered', ], // Service confirmed by supplier, optional connection date 'PROVISION-PLANNED' => [ 'fail'=>false, 'next'=>[ 'PROVISIONED'=>['wholesaler'], ], 'system'=>FALSE, 'method'=>'action_provision_planned', 'title'=>'Provision Planned', ], // Service has been provisioned by supplier 'PROVISIONED' => [ 'fail'=>false, 'next'=>[ 'ACTIVE'=>['wholesaler'], ], 'system'=>FALSE, 'method'=>'action_provisioned', 'title'=>'Provisioned', ], // Service is Active 'ACTIVE' => [ 'fail'=>FALSE, 'next'=>[ 'UPGRADE-REQUEST'=>['customer'], 'CANCEL-REQUEST'=>['customer'], ], 'system'=>FALSE, 'method'=>'action_active', 'title'=>'Service Active', ], // Service to be Upgraded 'UPGRADE-REQUEST' => [ 'fail'=>FALSE, 'next'=>[ 'UPGRADE-PENDING'=>[], ], 'system'=>FALSE, 'method'=>FALSE, 'title'=>'Upgrade Service', ], // Service to be Cancelled 'CANCEL-REQUEST' => [ 'fail'=>FALSE, 'next'=>[ 'CANCEL-PENDING'=>[], ], 'system'=>FALSE, 'method'=>'action_cancel_request', 'title'=>'Cancel Service', ], ]; /** * Account the service belongs to * * @return BelongsTo */ public function account() { return $this->belongsTo(Account::class); } /** * Return automatic billing details * * @return HasOne */ public function billing() { return $this->hasOne(AccountBilling::class); } /** * Return Charges associated with this Service * * @return 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_start'); 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 * * @return BelongsTo */ public function orderedby() { return $this->belongsTo(Account::class); } /** * Product of the service * * @return BelongsTo */ public function product() { return $this->belongsTo(Product::class); } /** * The site this service is configured for * * @todo It may be more appropriate to get this from the account->user attribute (ie: for mail actions) * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function site() { return $this->belongsTo(Site::class); } /** * Return a child model with details of the service * * @return 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(); } /** * Return the auto billing details * * @return mixed */ public function getAutoPayAttribute() { return $this->billing; } 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); } public function getBillingMonthlyPriceAttribute(): float { $d = 0; switch ($this->recur_schedule) { case 0: $d = 12/52; break; case 1: $d = 1; break; case 2: $d = 3; break; case 3: $d = 6; break; case 4: $d = 12; break; case 5: $d = 24; break; case 6: $d = 36; break; } return number_format($this->getBillingPriceAttribute()/$d,2); } /** * 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 Change date_next_invoice to connect_date/invoice_start_date * @return Carbon|string */ public function getInvoiceNextAttribute() { $last = $this->getInvoiceToAttribute(); $date = $last ? $last->addDay() : ($this->date_next_invoice ? $this->date_next_invoice->clone() : ($this->date_start ?: Carbon::now())); return request()->wantsJson() ? $date->format('Y-m-d') : $date; } /** * Return the end date for the next invoice * * @return mixed * @throws Exception */ public function getInvoiceNextEndAttribute() { switch ($this->recur_schedule) { // Weekly case 0: $date = $this->product->price_recurr_strict ? $this->getInvoiceNextAttribute()->endOfWeek() : $this->getInvoiceNextAttribute()->addWeek()->subDay(); break; // Monthly case 1: $date = $this->product->price_recurr_strict ? $this->getInvoiceNextAttribute()->endOfMonth() : $this->getInvoiceNextAttribute()->addMonth()->subDay(); break; // Quarterly case 2: $date = $this->product->price_recurr_strict ? $this->getInvoiceNextAttribute()->endOfQuarter() : $this->getInvoiceNextAttribute()->addQuarter()->subDay(); break; // Half Yearly case 3: $date = $this->product->price_recurr_strict ? $this->getInvoiceNextAttribute()->endOfHalf() : $this->getInvoiceNextAttribute()->addQuarter(2)->subDay(); break; // Yearly case 4: $date = $this->product->price_recurr_strict ? $this->getInvoiceNextAttribute()->endOfYear() : $this->getInvoiceNextAttribute()->addYear()->subDay(); break; // Two Yearly // NOTE: price_recurr_strict ignored case 5: $date = $this->getInvoiceNextAttribute()->addYear(2)->subDay(); break; // Three Yearly // NOTE: price_recurr_strict ignored case 6: $date = $this->getInvoiceNextAttribute()->addYear(3)->subDay(); break; default: throw new Exception('Unknown recur_schedule'); } // If the invoice has an end date, our invoice period shouldnt be greater than that. if ($this->date_end AND $this->date_end < $date) $date = $this->date_end; return $date; } public function getInvoiceNextQuantityAttribute() { // If we are not rounding to the first day of the cycle, then it is always a full cycle if (! $this->product->price_recurr_strict) return 1; $n = $this->invoice_next->diff($this->invoice_next_end)->days+1; switch ($this->recur_schedule) { // Weekly case 0: $d = $this->invoice_next->addWeek()->diff($this->invoice_next_end->startOfWeek())->days; break; // Monthly case 1: $d = $this->invoice_next->addMonth()->diff($this->invoice_next_end->startOfMonth())->days; break; // Quarterly case 2: $d = $this->invoice_next->addQuarter()->diff($this->invoice_next_end->startOfQuarter())->days; break; // Half Yearly case 3: $d = $this->invoice_next->addHalf()->diff($this->invoice_next_end->startOfHalf())->days; break; // Yearly case 4: $d = $this->invoice_next->addYear()->diff($this->invoice_next_end->startOfYear())->days; break; // Two Yearly case 5: $d = $this->invoice_next->addYear(2)->diff($this->invoice_next_end->subyear(2))->days-1; break; // Three Yearly case 6: $d = $this->invoice_next->addYear(3)->diff($this->invoice_next_end->subyear(3))->days-1; break; default: throw new Exception('Unknown recur_schedule'); } return round($n/$d,2); } /** * Get the date that the service has been invoiced to */ public function getInvoiceToAttribute() { $result = ($x=$this->invoice_items->filter(function($item) { return $item->item_type === 0;}))->count() ? $x->last()->date_stop : NULL; // For SSL Certificates, the invoice_to date is the expiry date of the Cert if (is_null($result) AND $this->type AND $this->type->type == 'ssl' AND $this->type->valid_to) return $this->type->valid_to; return $result; } 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->type ? $this->type->name : $this->id; } /** * @deprecated see getInvoiceNextAttribute() */ public function getNextInvoiceAttribute() { return $this->getInvoiceNextAttribute(); } public function getOrderInfoNotesAttribute(): ?string { return $this->getOrderInfoValue('notes'); } public function getOrderInfoReferenceAttribute(): ?string { return $this->getOrderInfoValue('reference'); } /** * Work out when this service has been paid to. * * @todo This might need to be optimised */ public function getPaidToAttribute() { foreach ($this->invoices->reverse() as $o) { if ($o->due == 0) { return $o->items ->filter(function($item) { return $item->item_type === 0; }) ->last() ->date_stop; } } } /** * 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); } public function getRecurScheduleAttribute($value): int { // If recur_schedule not set, default to 2 return $value ?? 2; } /** * @deprecated see getSIDAttribute() */ public function getServiceIdAttribute(): string { 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 description. * For: * + Broadband, this is the service address * + Domains, blank * + Hosting, blank * + SSL, blank * * @return string */ public function getSDescAttribute(): string { return ($this->type AND $this->type->service_description) ? $this->type->service_description : 'Service Description NOT Defined for :'.($this->type ? $this->type->type : $this->id); } /** * Return the service name. * For: * + Broadband, this is the service number * + Domains, this is the full domain name * + Hosting, this is the full domain name * + SSL, this is the DN * * @return string */ public function getSNameAttribute(): string { return ($this->type AND $this->type->service_name) ? $this->type->service_name : 'Service Name NOT Defined for :'.($this->type ? $this->type->type : $this->id); } /** * Return the service product type * This is used for view specific details * * @return string */ public function getSTypeAttribute(): string { switch($this->product->model) { case 'App\Models\Product\Adsl': return 'broadband'; default: return $this->type->type; } } /** * 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; if ($this->isPending()) $class = 'badge-warning'; else switch ($this->status) { case 'ACTIVE': $class = 'badge-success'; break; case 'INACTIVE': $class = 'badge-danger'; break; } return $class ? sprintf('%s',$class,$this->status) : $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 **/ // The action methods will return: NULL for no progress|FALSE for a failed status|next stage name. /** * Action required before order can leave the ACTIVE status. * * @return bool */ private function action_active(): ?bool { // N/A return TRUE; } /** * Request cancellation for an order when status ACTIVE stage. * This method should have the client confirm/accept the cancellation, if it was placed by a reseller/wholesaler. * * @return bool */ private function action_cancel_request(): ?bool { throw new HttpException(301,url('u/service/cancel',$this->id)); } /** * Processing when service has been ordered. * * @return bool|null */ private function action_ordered(): ?bool { // N/A return TRUE; } /** * Process for an order when status ORDER-ACCEPT stage. * This method should have the client confirm/accept the order, if it was placed by a reseller/wholesaler. * * @return bool */ private function action_order_accept(): ?bool { // @todo TO IMPLEMENT return TRUE; } /** * Action method when status ORDER_SENT * This method redirects to a form, where updating the form will progress to the next stage. */ private function action_order_sent(string $next) { // We can proceed to the ordered status if ($next == 'ORDERED' AND $this->order_info_reference) return TRUE; throw new HttpException(301,url('r/service/update',$this->id)); } /** * Action method when status ORDER_SUBMIT * * @return bool */ private function action_order_submit(): ?bool { // @todo TO IMPLEMENT return TRUE; } /** * Action when supplier has confirmed provisioning. * * @param string $next * @return bool */ private function action_provision_planned(string $next) { throw new HttpException(301,url('r/service/update',$this->id)); } /** * Process for an order when status SETUP-PAYMENT-WAIT stage. * This method should collect any setup fees payment. * * @return bool */ private function action_setup_payment_wait(): ?bool { // @todo TO IMPLEMENT return TRUE; } /** * Process for an order when status PAYMENT-CHECK stage. * This method should validate any payment details. * * @return bool */ private function action_payment_check(): ?bool { // @todo TO IMPLEMENT return TRUE; } /** * Process for an order when status PAYMENT-WAIT stage. * This method should collect any service payment details. * * @return bool */ private function action_payment_wait(): ?bool { // @todo TO IMPLEMENT return TRUE; } private function getOrderInfoValue(string $key): ?string { return $this->order_info ? $this->order_info->get($key) : NULL; } /** * Get the current stage parameters * * @param string $stage * @return array */ private function getStageParameters(string $stage): Collection { $result = collect(Arr::get(self::$action_progress,$stage)); $myrole = array_search(Auth::user()->role(),User::$role_order); // If we have no valid next stage, return an empty collection. if (! $result->count() OR $myrole===FALSE) return $result; // Filter the result based on who we are $next = collect(); foreach ($result->get('next') as $action=>$roles) { // Can the current user do this role? $cando = FALSE; foreach ($roles as $role) { if ($myrole <= array_search($role,User::$role_order)) { $cando = TRUE; break; } } if ($cando OR $result->get('system')) { $next->put($action,$roles); } } $result->put('next',$next); return $result; } /** * @notes * + When progressing stages, we know who the user is that initiated the stage, * + If no user, then we perform stages SYSTEM=TRUE * + We need to validate that the current stage is complete, before progressing to the next stage * + The current stage may require input from a user, or automation process to progress * + Before leaving this method, we update the service with the stage that it is currently on. * * @param string $stage * @return bool|int|string|null */ public function action(string $stage) { // While stage has a string value, that indicates the next stage we want to go to // If stage is NULL, the current stage hasnt been completed // If stage is FALSE, then the current stage failed, and may optionally be directed to another stage. while ($stage) { // Check that stage is a valid next action for the user currently performing it $current = $this->getStageParameters($this->order_status); $next = $this->getStageParameters($stage); // If valid, call the method to confirm that the current stage is complete if (method_exists($this,$current['method'])) { try { $result = $this->{$current['method']}($stage); // If we have a form to complete, we need to return with a URL, so we can catch that with an Exception } catch (HttpException $e) { if ($e->getStatusCode() == 301) return ($e->getMessage()); } // @todo Implement a status message if (is_null($result)) { $stage = NULL; abort(500,'Current Method Cannot Proceed: '.$result); // @todo Implement a status message } elseif (! $result) { $stage = NULL; abort(500,'Current Method FAILED: '.$result); } else { $this->order_status = $stage; $this->save(); // If we have more than 1 next step for the next stage, we'll have to end. if ($this->actions()->count() > 1) { $stage = NULL; } else { $stage = $this->actions()->keys()->first(); } } // @todo Implement a status message } else { // Cant do anything, dont have a method to check if we can leave $stage = NULL; abort(500,'NO Method Cannot Proceed to leave this stage: '.$current['method']); } // If valid, call the method to start the next stage } } /** * Work out the next applicable actions for this service status * * @notes * + Clients can only progress 1 step, if they are in the next step. * + Resellers/Wholesales can progress to the next Reseller/Wholesaler and any steps in between. * * @param bool $next Only show next actions * @return Collection */ public function actions(): Collection { $result = collect(); $action = $this->getStageParameters($this->order_status); if (! $action->count()) return $result; // Next Action foreach ($this->getStageParameters($this->order_status)->get('next') as $k=>$v) { $result->put($k,Arr::get(self::$action_progress,$k.'.title')); } // No next actions, that will mean the current action hasnt completed. if (! $result->count()) $result->put($this->order_status,Arr::get($action,'title')); return $result; } /** * 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(): DatabaseCollection { $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)); } /** * Identify if a service is being ordered * * @return bool */ public function isPending(): bool { return ! $this->active AND ! is_null($this->order_status) AND ! in_array($this->order_status,array_merge($this->inactive_status,['INACTIVE'])); } /** * Generate a collection of invoice_item objects that will be billed for the next invoice * * @param bool $future Next item to be billed (not in the next x days) * @return Collection * @throws Exception */ public function next_invoice_items(bool $future): Collection { if ($this->wasCancelled() OR $this->suspend_billing OR $this->external_billing OR (! $future AND ! $this->active)) return collect(); // If pending, add any connection charges // Connection charges are only charged once if ((! $this->invoice_items->filter(function($item) { return $item->item_type==4; })->sum('total')) AND ($this->isPending() OR is_null($this->invoice_to)) AND $this->product->price($this->recur_schedule,'price_setup')) { $o = new InvoiceItem; $o->active = TRUE; $o->service_id = $this->id; $o->product_id = $this->product_id; $o->item_type = 4; // @todo change to const or something $o->price_base = $this->product->price($this->recur_schedule,'price_setup'); // @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; $o->quantity = 1; $o->site_id = 1; // @todo $o->addTaxes($this->account->country->taxes); $this->invoice_items->push($o); } // If the service is active, there will be service charges if ((! $this->invoice_items->filter(function($item) { return $item->item_type==0 AND ! $item->exists; })->count()) AND ($this->active OR $this->isPending()) AND ( ($future == TRUE AND $this->invoice_next < $this->invoice_next_end) OR ($future == FALSE AND ($this->invoice_to < ($this->date_end ?: Carbon::now()->addDays(30)))) )) { do { $o = new InvoiceItem; $o->active = TRUE; $o->service_id = $this->id; $o->product_id = $this->product_id; $o->item_type = 0; $o->price_base = is_null($this->price) ? (is_null($this->price_override) ? $this->product->price($this->recur_schedule) : $this->price_override) : $this->price; // @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->quantity = $this->invoice_next_quantity; $o->site_id = 1; // @todo $o->addTaxes($this->account->country->taxes); $this->invoice_items->push($o); } while ($future == FALSE AND ($this->invoice_to < ($this->date_end ?: Carbon::now()->addDays(30)))); } // Add additional charges if (($future == TRUE OR ($future == FALSE AND ($this->invoice_to >= Carbon::now()->addDays(30)))) AND ! $this->invoice_items->filter(function($item) { return $item->module_id == 30 AND ! $item->exists; })->count()) { 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->site_id = 1; // @todo $o->addTaxes($this->account->country->taxes); $this->invoice_items->push($o); } } return $this->invoice_items->filter(function($item) { return ! $item->exists; }); } /** * 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; } } /** * Store order info details * * @param string $key * @param string $value */ public function setOrderInfo(string $key,?string $value): void { $x = is_null($this->order_info) ? collect() : $this->order_info; $x->put($key,$value); $this->order_info = $x; } /** * Service that was cancelled or never provisioned * * @return bool */ public function wasCancelled(): bool { return in_array($this->order_status,$this->inactive_status); } }