diff --git a/app/Models/Cost.php b/app/Models/Cost.php index 97408ea..403f865 100644 --- a/app/Models/Cost.php +++ b/app/Models/Cost.php @@ -11,8 +11,8 @@ class Cost extends Model { use HasFactory; - protected $dates = [ - 'billed_at', + protected $casts = [ + 'billed_at' => 'datetime:Y-m-d', ]; protected $with = [ diff --git a/app/Models/Service.php b/app/Models/Service.php index 4200f5c..eaddfa8 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -2,20 +2,17 @@ namespace App\Models; -use Carbon\Carbon; use Exception; use Illuminate\Database\Eloquent\Casts\AsCollection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Database\Eloquent\Relations\HasOne; -use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; use Symfony\Component\HttpKernel\Exception\HttpException; -use Leenooks\Carbon as LeenooksCarbon; +use Leenooks\Carbon; +use Leenooks\Casts\LeenooksCarbon; use App\Models\Product\Type; use App\Interfaces\IDs; @@ -48,7 +45,7 @@ use App\Traits\ScopeServiceUserAuthorised; * + supplied : The model of the supplier's product used for this service. * * Methods: - * + isChargeOverriden : Has the price been overridden? + * + isChargeOverridden : Has the price been overridden? * + isPending : Is this a pending active service * * @package App\Models @@ -61,33 +58,9 @@ class Service extends Model implements IDs protected $casts = [ 'order_info' => AsCollection::class, - 'invoice_last_at' => 'datetime:Y-m-d', // @todo Can these be removed, since we can work out invoice dynamically now - 'invoice_next_at' => 'datetime:Y-m-d', // @todo Can these be removed, since we can work out invoice dynamically now - 'stop_at' => 'datetime:Y-m-d', - 'start_at' => 'datetime:Y-m-d', - ]; - - /** @deprecated */ - protected $appends = [ - 'category_name', - 'name_short', - ]; - - /** @deprecated */ - protected $visible = [ - // 'account_name', - // 'admin_service_id_url', - 'active', - 'category_name', - // 'billing_price', - // 'data_orig', - 'id', - 'name_short', - // 'next_invoice', - // 'product.name', - // 'service_id', - // 'service_id_url', - // 'status', + 'invoice_next_at' => LeenooksCarbon::class, // @todo Can this be removed, since start_at provides this functionality + 'stop_at' => LeenooksCarbon::class, + 'start_at' => LeenooksCarbon::class, ]; protected $with = [ @@ -295,6 +268,26 @@ class Service extends Model implements IDs ], ]; + /* STATIC */ + + /** + * List of services that are being changed + * + * Services are being changed, when they are active, but their order status is not active + * + * @param User $uo + * @return Collection + */ + public static function movements(User $uo): Collection + { + return (new self) + ->active() + ->serviceUserAuthorised($uo) + ->where('order_status','!=','ACTIVE') + ->with(['account','product']) + ->get(); + } + /* INTERFACES */ /** @@ -317,24 +310,10 @@ class Service extends Model implements IDs return sprintf('%02s-%04s.%s',$this->site_id,$this->account_id,$this->getLIDattribute()); } - /* STATIC */ - - public static function movements(User $uo): Collection - { - return (new self) - ->active() - ->serviceUserAuthorised($uo) - ->where('order_status','!=','ACTIVE') - ->with(['account','product']) - ->get(); - } - /* RELATIONS */ /** * Account the service belongs to - * - * @return BelongsTo */ public function account() { @@ -343,8 +322,6 @@ class Service extends Model implements IDs /** * Return automatic billing details - * - * @return HasOne */ public function billing() { @@ -353,8 +330,6 @@ class Service extends Model implements IDs /** * Return Charges associated with this Service - * - * @return HasMany */ public function charges() { @@ -424,8 +399,7 @@ class Service extends Model implements IDs { return $this->hasMany(InvoiceItem::class) ->where('item_type','<>',0) - ->orderBy('service_id') - ->orderBy('start_at'); + ->orderBy('start_at','desc'); } /** @@ -444,8 +418,7 @@ class Service extends Model implements IDs { return $this->hasMany(InvoiceItem::class) ->where('item_type','=',0) - ->orderBy('service_id') - ->orderBy('start_at'); + ->orderBy('start_at','desc'); } /** @@ -457,10 +430,14 @@ class Service extends Model implements IDs ->where('active',TRUE); } + public function invoiced_service_items_active_recent() + { + return $this->invoiced_service_items_active() + ->limit(10); + } + /** * User that ordered the service - * - * @return BelongsTo */ public function orderedby() { @@ -469,8 +446,6 @@ class Service extends Model implements IDs /** * Product of the service - * - * @return BelongsTo */ public function product() { @@ -479,8 +454,6 @@ class Service extends Model implements IDs /** * Return a child model with details of the service - * - * @return MorphTo */ public function type() { @@ -548,16 +521,32 @@ class Service extends Model implements IDs /** * How much do we charge for this service, base on the current recur schedule + * price in the DB overrides the base price used * * @return float */ public function getBillingChargeAttribute(): float { - // @todo Temporary for services that dont have recur_schedule set. - if (is_null($this->recur_schedule) OR is_null($this->product->getBaseChargeAttribute($this->recur_schedule,$this->account->group))) - $this->price=0; + // If recur_schedule is null, then we only bill this item once + if (is_null($this->recur_schedule) && $this->getInvoiceToAttribute()) + $this->price = 0; - return $this->addTax(is_null($this->price) ? $this->product->getBaseChargeAttribute($this->recur_schedule,$this->account->group) : $this->price); + return $this->account->taxed( + is_null($this->price) + ? $this->product->getBaseChargeAttribute($this->recur_schedule,$this->account->group) + : $this->price + ); + } + + /** + * Determine a monthly price for a service, even if it is billed at a different frequency + * + * @return float + * @throws Exception + */ + public function getBillingChargeNormalisedAttribute(): float + { + return number_format($this->getBillingChargeAttribute()*Invoice::billing_change($this->recur_schedule,$this->offering->billing_interval),2); } /** @@ -585,18 +574,29 @@ class Service extends Model implements IDs * * @return float * @throws Exception - * @deprecated use class::charge_normalized() + * @deprecated use class::billing_charge_normalised() */ public function getBillingMonthlyPriceAttribute(): float { - return number_format($this->getBillingChargeAttribute()/Arr::get(Invoice::billing_periods,$this->recur_schedule.'.interval',1),2); + Log::alert('SMO:! Deprecated function getBillingMonthlyPriceAttribute()'); + return $this->getBillingChargeNormalisedAttribute(); } + /** + * Service Category ID + * + * @return string + */ public function getCategoryAttribute(): string { return $this->product->category; } + /** + * Service Category Name + * + * @return string + */ public function getCategoryNameAttribute(): string { return $this->product->category_name; @@ -607,7 +607,7 @@ class Service extends Model implements IDs * * Service contracts end the later of the start_date + contract_term or the expire date. * - * @return Carbon + * @return Carbon|null */ public function getContractEndAttribute(): ?Carbon { @@ -640,49 +640,32 @@ class Service extends Model implements IDs return max($this->supplied->contract_term,$this->product->type->contract_term); } - /** - * Date the service expires, also represents when it is paid up to - * - * @return string - * @todo - */ - public function getExpiresAttribute(): string - { - abort(500,'Not implemented'); - return 'TBA'; - } - /** * Return the date for the next invoice * - * @return LeenooksCarbon + * In priority order, the next is either + * + The next day after it was last invoiced to (stop_at) + * + The earlier of invoice_next_at and start_at dates + * + Today + * + * @return Carbon */ - public function getInvoiceNextAttribute(): LeenooksCarbon + public function getInvoiceNextAttribute(): Carbon { $last = $this->getInvoiceToAttribute(); + return $last ? $last->addDay() - : ($this->invoice_next_at ? $this->invoice_next_at->clone() : ($this->start_at ?: LeenooksCarbon::now())); - } - - /** - * We need to cast some dates to LeenooksCarbon to get access to startOfHalf()/endOfHalf() methods - * - * @param $value - * @return LeenooksCarbon|null - */ - public function getInvoiceNextAtAttribute($value): ?LeenooksCarbon - { - return $value ? LeenooksCarbon::create($value) : NULL; + : (min($this->start_at,$this->invoice_next_at) ?: Carbon::now()); } /** * Return the end date for the next invoice * - * @return mixed + * @return Carbon * @throws Exception */ - public function getInvoiceNextEndAttribute() + public function getInvoiceNextEndAttribute(): Carbon { switch ($this->recur_schedule) { case Invoice::BILL_WEEKLY: @@ -706,7 +689,7 @@ class Service extends Model implements IDs case Invoice::BILL_SEMI_YEARLY: $date = $this->product->price_recur_strict ? $this->getInvoiceNextAttribute()->endOfHalf() - : $this->getInvoiceNextAttribute()->addQuarter(2)->subDay(); + : $this->getInvoiceNextAttribute()->addQuarters(2)->subDay(); break; case Invoice::BILL_YEARLY: @@ -717,11 +700,12 @@ class Service extends Model implements IDs case Invoice::BILL_TWOYEARS: if (! $this->product->price_recur_strict) { - $date = $this->getInvoiceNextAttribute()->addYear(2)->subDay(); + $date = $this->getInvoiceNextAttribute()->addYears(2)->subDay(); } else { - $date = $this->getInvoiceNextAttribute()->addYear(2)->subDay()->endOfYear(); + $date = $this->getInvoiceNextAttribute()->addYears(2)->subDay()->endOfYear(); + // Make sure we end on an even year if ($date->clone()->addDay()->year%2) $date = $date->subYear(); } @@ -729,25 +713,25 @@ class Service extends Model implements IDs // NOTE: price_recur_strict ignored case Invoice::BILL_THREEYEARS: - $date = $this->getInvoiceNextAttribute()->addYear(3)->subDay(); + $date = $this->getInvoiceNextAttribute()->addYears(3)->subDay(); break; // NOTE: price_recur_strict ignored case Invoice::BILL_FOURYEARS: - $date = $this->getInvoiceNextAttribute()->addYear(4)->subDay(); + $date = $this->getInvoiceNextAttribute()->addYears(4)->subDay(); break; // NOTE: price_recur_strict ignored case Invoice::BILL_FIVEYEARS: - $date = $this->getInvoiceNextAttribute()->addYear(5)->subDay(); + $date = $this->getInvoiceNextAttribute()->addYears(5)->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->stop_at AND $this->stop_at < $date) + // If the invoice has an end date, our invoice period shouldnt be greater than that (might be terminating). + if ($this->stop_at && ($this->stop_at < $date)) $date = $this->stop_at; return $date; @@ -765,43 +749,43 @@ class Service extends Model implements IDs if (! $this->product->price_recur_strict) return 1; - $n = round($this->invoice_next->diffInDays($this->invoice_next_end),0); + $n = $this->invoice_next->diffInDays($this->invoice_next_end); switch ($this->recur_schedule) { case Invoice::BILL_WEEKLY: - $d = $this->invoice_next->addWeek()->startOfWeek()->diff($this->invoice_next_end->startOfWeek())->days; + $d = $this->invoice_next_end->addWeek()->startOfWeek()->diffInDays($this->invoice_next->startOfWeek()); break; case Invoice::BILL_MONTHLY: - $d = $this->invoice_next->addMonth()->startOfMonth()->diff($this->invoice_next_end->startOfMonth())->days; + $d = $this->invoice_next_end->addMonth()->startOfMonth()->diffInDays($this->invoice_next->startOfMonth()); break; case Invoice::BILL_QUARTERLY: - $d = round($this->invoice_next_end->startOfQuarter()->diffInDays($this->invoice_next->addQuarter()->startOfQuarter()),1); + $d = $this->invoice_next_end->startOfQuarter()->diffInDays($this->invoice_next->addQuarter()->startOfQuarter()); break; case Invoice::BILL_SEMI_YEARLY: - $d = $this->invoice_next->addQuarter(2)->startOfHalf()->diff($this->invoice_next_end->startOfHalf())->days; + $d = $this->invoice_next_end->addQuarter(2)->startOfHalf()->diffInDays($this->invoice_next->startOfHalf()); break; case Invoice::BILL_YEARLY: - $d = $this->invoice_next->addYear()->startOfYear()->diff($this->invoice_next_end->startOfYear())->days; + $d = $this->invoice_next_end->addYear()->startOfYear()->diffInDays($this->invoice_next->startOfYear()); break; case Invoice::BILL_TWOYEARS: - $d = $this->invoice_next->addYear(2)->startOfYear()->diff($this->invoice_next_end->subyear(2))->days-1; + $d = $this->invoice_next_end->addYear(2)->startOfYear()->diffInDays($this->invoice_next->subyear(2))-1; break; case Invoice::BILL_THREEYEARS: - $d = $this->invoice_next->addYear(3)->startOfYear()->diff($this->invoice_next_end->subyear(3))->days-1; + $d = $this->invoice_next_end->addYear(3)->startOfYear()->diffInDays($this->invoice_next->subyear(3))-1; break; case Invoice::BILL_FOURYEARS: - $d = $this->invoice_next->addYear(3)->startOfYear()->diff($this->invoice_next_end->subyear(4))->days-1; + $d = $this->invoice_next_end->addYear(3)->startOfYear()->diffInDays($this->invoice_next->subyear(4))-1; break; case Invoice::BILL_FIVEYEARS: - $d = $this->invoice_next->addYear(3)->startOfYear()->diff($this->invoice_next_end->subyear(5))->days-1; + $d = $this->invoice_next_end->addYear(3)->startOfYear()->diffInDays($this->invoice_next->subyear(5))-1; break; default: @@ -814,12 +798,12 @@ class Service extends Model implements IDs /** * Get the date that the service has been invoiced to * - * @return LeenooksCarbon|null + * @return Carbon|null */ - public function getInvoiceToAttribute(): ?LeenooksCarbon + public function getInvoiceToAttribute(): ?Carbon { - return ($x=$this->invoice_items->filter(function($item) { return $item->item_type === 0;}))->count() - ? $x->last()->stop_at + return ($x=$this->invoiced_service_items_active_recent)->count() + ? $x->first()->stop_at : NULL; } @@ -863,7 +847,7 @@ class Service extends Model implements IDs /** * The product we supply for this service * - * @return Model + * @return Type */ public function getOfferingAttribute(): Type { @@ -872,12 +856,12 @@ class Service extends Model implements IDs public function getOrderInfoNotesAttribute(): ?string { - return $this->getOrderInfoValue('notes'); + return $this->orderInfo('notes'); } public function getOrderInfoReferenceAttribute(): ?string { - return $this->getOrderInfoValue('reference'); + return $this->orderInfo('reference'); } /** @@ -904,23 +888,12 @@ class Service extends Model implements IDs * @param $value * @return int */ - public function getRecurScheduleAttribute($value): int + public function xgetRecurScheduleAttribute($value): int { // If recur_schedule not set, default to quarterly return $value ?? Invoice::BILL_QUARTERLY; } - /** - * We need to cast some dates to LeenooksCarbon to get access to startOfHalf()/endOfHalf() methods - * - * @param $value - * @return LeenooksCarbon - */ - public function getStartAtAttribute($value): LeenooksCarbon - { - return LeenooksCarbon::create($value); - } - /** * Return the Service Status * @@ -928,39 +901,7 @@ class Service extends Model implements IDs */ public function getStatusAttribute(): string { - if (! $this->order_status) - return $this->active ? 'ACTIVE' : 'INACTIVE'; - else - return $this->order_status; - } - - /** - * Return an HTML status box - * - * @return string - * @todo Add in the other status' - */ - 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; + return $this->active ? $this->order_status : 'INACTIVE'; } /** @@ -1107,34 +1048,6 @@ class Service extends Model implements IDs : collect(); } - /** - * Add applicable tax to the cost - * - * @todo This needs to be calculated, not fixed at 1.1 - * @todo move all tax calculations into product - * @param float $value - * @return float - */ - private function addTax(float $value): float - { - return round($value*1.1,2); - } - - /** - * Return a normalize price dependent on the product, ie: Broadband = Monthly, Domain = Yearly, etc - * - * @return float - */ - public function charge_normalized(): float - { - return number_format($this->getBillingChargeAttribute()*Invoice::billing_change($this->recur_schedule,$this->offering->billing_interval),2); - } - - private function getOrderInfoValue(string $key): ?string - { - return $this->order_info ? $this->order_info->get($key) : NULL; - } - /** * Get the stage parameters * @@ -1176,16 +1089,6 @@ class Service extends Model implements IDs return collect($result); } - /** - * Does this service have traffic data to be graphed - * - * @return bool - */ - public function hasUsage(): bool - { - return $this->product->hasUsage(); - } - /** * Return this service invoices */ @@ -1200,11 +1103,10 @@ class Service extends Model implements IDs * Determine if a service is active. It is active, if active=1, or the order_status is not in self::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,self::INACTIVE_STATUS)); + return $this->attributes['active'] || ($this->order_status && (! in_array($this->order_status,self::INACTIVE_STATUS))); } /** @@ -1214,7 +1116,7 @@ class Service extends Model implements IDs * @return bool * @todo Can we use the gates to achieve this? */ - public function isAuthorised(string $role): bool + private function isAuthorised(string $role): bool { switch(Auth::user()->role()) { // Wholesalers are site admins, they can see everything @@ -1267,10 +1169,11 @@ class Service extends Model implements IDs } /** - * Has the price for this service been override + * Has the price for this service been overridden + * * @return bool */ - public function isChargeOverriden(): bool + public function isChargeOverridden(): bool { return ! is_null($this->price); } @@ -1293,9 +1196,9 @@ class Service extends Model implements IDs */ public function isPending(): bool { - return ! $this->active - AND ! is_null($this->order_status) - AND ! in_array($this->order_status,array_merge(self::INACTIVE_STATUS,['INACTIVE'])); + return (! $this->active) + && (! is_null($this->order_status)) + && (! in_array($this->order_status,array_merge(self::INACTIVE_STATUS,['INACTIVE']))); } /** @@ -1392,6 +1295,18 @@ class Service extends Model implements IDs return $this->invoice_items->filter(function($item) { return ! $item->exists; }); } + /** + * Extract data from the order_info column + * + * @param string $key + * @return string|null + * @todo This should be a json column, so we shouldnt need this by using order_info->key + */ + private function orderInfo(string $key): ?string + { + return $this->order_info ? $this->order_info->get($key) : NULL; + } + /** * Service that was cancelled or never provisioned * diff --git a/app/Traits/QueryCacheableConfig.php b/app/Traits/QueryCacheableConfig.php deleted file mode 100644 index cfdeaa4..0000000 --- a/app/Traits/QueryCacheableConfig.php +++ /dev/null @@ -1,17 +0,0 @@ -text('prod_attr')->nullable(); $table->string('model')->nullable(); - $table->string('order_status')->nullable(); + $table->string('order_status'); $table->text('order_info')->nullable(); $table->date('invoice_last_at')->nullable(); diff --git a/resources/views/theme/backend/adminlte/product/widget/services.blade.php b/resources/views/theme/backend/adminlte/product/widget/services.blade.php index f094746..ae50eec 100644 --- a/resources/views/theme/backend/adminlte/product/widget/services.blade.php +++ b/resources/views/theme/backend/adminlte/product/widget/services.blade.php @@ -24,7 +24,7 @@
Status | -{!! $o->status_html !!} | +@include('theme.backend.adminlte.service.widget.status') | ||||||
---|---|---|---|---|---|---|---|---|
Amount | - @if($o->isChargeOverriden()) + @if($o->isChargeOverridden())@if ($o->billing_charge < $o->charge_orig) |
@else
${{ number_format($o->billing_charge,2) }} | diff --git a/resources/views/theme/backend/adminlte/service/widget/internal.blade.php b/resources/views/theme/backend/adminlte/service/widget/internal.blade.php index 04325cc..e2e3114 100644 --- a/resources/views/theme/backend/adminlte/service/widget/internal.blade.php +++ b/resources/views/theme/backend/adminlte/service/widget/internal.blade.php @@ -61,11 +61,11 @@||||||
Billing Price | -isChargeOverriden())class="text-danger"@endif>${{ number_format($b=$o->billing_charge,2) }} | +isChargeOverridden())class="text-danger"@endif>${{ number_format($b=$o->billing_charge,2) }} | ${{ number_format($a=$o->account->taxed($c->base_cost),2) }} | {!! markup($a,$b) !!} | @if ($p->exists) -isChargeOverriden())class="text-danger"@endif>${{ number_format($b=$o->account->taxed($p->base_charge),2) }} | +isChargeOverridden())class="text-danger"@endif>${{ number_format($b=$o->account->taxed($p->base_charge),2) }} | ${{ number_format($a=$o->account->taxed($p->base_cost),2) }} | {!! markup($a,$b) !!} | @endif @@ -73,7 +73,7 @@
Monthly Price | -isChargeOverriden()) class="text-danger" @endif> + | isChargeOverridden()) class="text-danger" @endif>
@if($x)
${{ number_format($b=$o->billing_monthly_price,2) }}
@else
@@ -83,7 +83,7 @@
${{ number_format($a=$o->account->taxed($c->base_cost)*\App\Models\Invoice::billing_change($o->billing_interval,\App\Models\Invoice::BILL_MONTHLY),2) }} |
{!! markup($a,$b) !!} |
@if ($p->exists)
- isChargeOverriden()) class="text-danger" @endif>${{ number_format($b=$o->account->taxed($p->base_charge)*\App\Models\Invoice::billing_change($o->billing_interval,\App\Models\Invoice::BILL_MONTHLY),2) }} |
+ isChargeOverridden()) class="text-danger" @endif>${{ number_format($b=$o->account->taxed($p->base_charge)*\App\Models\Invoice::billing_change($o->billing_interval,\App\Models\Invoice::BILL_MONTHLY),2) }} |
${{ number_format($a=$o->account->taxed($p->base_cost)*\App\Models\Invoice::billing_change($o->billing_interval,\App\Models\Invoice::BILL_MONTHLY),2) }} |
{!! markup($a,$b) !!} |
@endif
|