Invoice rendering for service, and unit testing for Invoice Item quantity
This commit is contained in:
parent
5f5d114f42
commit
ebd4367975
@ -4,6 +4,7 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
use Leenooks\Carbon;
|
||||
|
||||
class InvoiceItem extends Model
|
||||
{
|
||||
@ -36,6 +37,36 @@ class InvoiceItem extends Model
|
||||
|
||||
/** ATTRIBUTES **/
|
||||
|
||||
/**
|
||||
* Start date for the invoice item line
|
||||
*
|
||||
* We need cast this value to a Leenooks\Carbon for access to startOfHalf() endOfHalf() methods
|
||||
*
|
||||
* @param $value
|
||||
* @return Carbon
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getDateStartAttribute($value)
|
||||
{
|
||||
if (! is_null($value))
|
||||
return Carbon::createFromTimestamp($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* End date for the invoice item line
|
||||
*
|
||||
* We need cast this value to a Leenooks\Carbon for access to startOfHalf() endOfHalf() methods
|
||||
*
|
||||
* @param $value
|
||||
* @return Carbon
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getDateStopAttribute($value)
|
||||
{
|
||||
if (! is_null($value))
|
||||
return Carbon::createFromTimestamp($value);
|
||||
}
|
||||
|
||||
public function getItemTypeNameAttribute()
|
||||
{
|
||||
$types = [
|
||||
|
@ -2,15 +2,18 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Collection as DatabaseCollection;
|
||||
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 App\Traits\NextKey;
|
||||
use Leenooks\Carbon;
|
||||
|
||||
class Service extends Model
|
||||
{
|
||||
@ -23,8 +26,11 @@ class Service extends Model
|
||||
|
||||
protected $dates = [
|
||||
'date_last_invoice',
|
||||
'date_next_invoice'
|
||||
'date_next_invoice'.
|
||||
'date_start',
|
||||
'date_end',
|
||||
];
|
||||
|
||||
public $dateFormat = 'U';
|
||||
protected $table = 'ab_service';
|
||||
|
||||
@ -303,38 +309,124 @@ class Service extends Model
|
||||
* Return the date for the next invoice
|
||||
*
|
||||
* @todo This function negates the need for date_next_invoice
|
||||
* @return null
|
||||
* @return Carbon|string
|
||||
*/
|
||||
public function getInvoiceNextAttribute()
|
||||
{
|
||||
$last = $this->getInvoiceToAttribute();
|
||||
$date = $last ? $last->addDay() : now();
|
||||
$date = $last ? $last->addDay() : 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)
|
||||
{
|
||||
switch ($this->recur_schedule) {
|
||||
// Weekly
|
||||
case 0: $date = $this->getInvoiceNextAttribute()->addWeek(); break;
|
||||
case 0: $date = $this->product->price_recurr_strict
|
||||
? $this->getInvoiceNextAttribute()->endOfWeek()
|
||||
: $this->getInvoiceNextAttribute()->addWeek()->subDay();
|
||||
break;
|
||||
|
||||
// Monthly
|
||||
case 1: $date = $this->getInvoiceNextAttribute()->addMonth(); break;
|
||||
case 1:
|
||||
$date = $this->product->price_recurr_strict
|
||||
? $this->getInvoiceNextAttribute()->endOfMonth()
|
||||
: $this->getInvoiceNextAttribute()->addMonth()->subDay();
|
||||
break;
|
||||
|
||||
// Quarterly
|
||||
case 2: $date = $this->getInvoiceNextAttribute()->addQuarter(); break;
|
||||
case 2:
|
||||
$date = $this->product->price_recurr_strict
|
||||
? $this->getInvoiceNextAttribute()->endOfQuarter()
|
||||
: $this->getInvoiceNextAttribute()->addQuarter()->subDay();
|
||||
break;
|
||||
|
||||
// Half Yearly
|
||||
case 3: $date = $this->getInvoiceNextAttribute()->addQuarter(2); break;
|
||||
case 3:
|
||||
$date = $this->product->price_recurr_strict
|
||||
? $this->getInvoiceNextAttribute()->endOfHalf()
|
||||
: $this->getInvoiceNextAttribute()->addQuarter(2)->subDay();
|
||||
break;
|
||||
|
||||
// Yearly
|
||||
case 4: $date = $this->getInvoiceNextAttribute()->addYear(); break;
|
||||
case 4:
|
||||
$date = $this->product->price_recurr_strict
|
||||
? $this->getInvoiceNextAttribute()->endOfYear()
|
||||
: $this->getInvoiceNextAttribute()->addYear()->subDay();
|
||||
break;
|
||||
|
||||
// Two Yearly
|
||||
case 5: $date = $this->getInvoiceNextAttribute()->addYear(2); break;
|
||||
// NOTE: price_recurr_strict ignored
|
||||
case 5: $date = $this->getInvoiceNextAttribute()->addYear(2)->subDay(); break;
|
||||
|
||||
// Three Yearly
|
||||
case 6: $date = $this->getInvoiceNextAttribute()->addYear(3); break;
|
||||
default: throw new \Exception('Unknown recur_schedule');
|
||||
// NOTE: price_recurr_strict ignored
|
||||
case 6: $date = $this->getInvoiceNextAttribute()->addYear(3)->subDay(); break;
|
||||
|
||||
default: throw new Exception('Unknown recur_schedule');
|
||||
}
|
||||
|
||||
return $date->subDay();
|
||||
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_end->diff($this->invoice_next_end->startOfWeek())->days;
|
||||
break;
|
||||
|
||||
// Monthly
|
||||
case 1:
|
||||
$d = $this->invoice_next_end->diff($this->invoice_next_end->startOfMonth())->days;
|
||||
break;
|
||||
|
||||
// Quarterly
|
||||
case 2:
|
||||
$d = $this->invoice_next_end->diff($this->invoice_next_end->startOfQuarter())->days;
|
||||
break;
|
||||
|
||||
// Half Yearly
|
||||
case 3:
|
||||
$d = $this->invoice_next_end->diff($this->invoice_next_end->startOfHalf())->days;
|
||||
break;
|
||||
|
||||
// Yearly
|
||||
case 4:
|
||||
$d = $this->invoice_next_end->diff($this->invoice_next_end->startOfYear())->days;
|
||||
break;
|
||||
|
||||
// Two Yearly
|
||||
case 5:
|
||||
$d = $this->invoice_next_end->diff($this->invoice_next_end->subyear(2))->days-1;
|
||||
break;
|
||||
|
||||
// Three Yearly
|
||||
case 6:
|
||||
$d = $this->invoice_next_end->diff($this->invoice_next_end->subyear(3))->days-1;
|
||||
break;
|
||||
|
||||
default: throw new Exception('Unknown recur_schedule');
|
||||
}
|
||||
|
||||
// Include the start date and end date.
|
||||
$d += 1;
|
||||
|
||||
return round($n/$d,2);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -541,14 +633,24 @@ class Service extends Model
|
||||
public function getStatusHTMLAttribute(): string
|
||||
{
|
||||
$class = NULL;
|
||||
switch ($this->status)
|
||||
{
|
||||
case 'ACTIVE':
|
||||
$class = 'badge-success';
|
||||
break;
|
||||
}
|
||||
|
||||
return sprintf('<span class="badge %s">%s</span>',$class,$this->status);
|
||||
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('<span class="badge %s">%s</span>',$class,$this->status)
|
||||
: $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -597,7 +699,7 @@ class Service extends Model
|
||||
return round($value*1.1,2);
|
||||
}
|
||||
|
||||
public function invoices_due(): Collection
|
||||
public function invoices_due(): DatabaseCollection
|
||||
{
|
||||
$this->load('invoice_items.invoice');
|
||||
|
||||
@ -635,29 +737,41 @@ class Service extends Model
|
||||
*/
|
||||
public function isPending(): bool
|
||||
{
|
||||
return ! $this->active AND ! in_array($this->order_status,$this->inactive_status);
|
||||
return ! $this->active
|
||||
AND ! is_null($this->order_status)
|
||||
AND ! in_array($this->order_status,array_merge($this->inactive_status,['INACTIVE']));
|
||||
}
|
||||
|
||||
public function next_invoice_items(): \Illuminate\Support\Collection
|
||||
/**
|
||||
* Generate a collection of invoice_item objects that will be billed for the next invoice
|
||||
*
|
||||
* @return Collection
|
||||
* @throws Exception
|
||||
*/
|
||||
public function next_invoice_items(): 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);
|
||||
// If the service is active, there will be service charges
|
||||
if ($this->active or $this->isPending()) {
|
||||
$o->active = TRUE;
|
||||
$o->service_id = $this->id;
|
||||
$o->product_id = $this->product_id;
|
||||
$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->quantity = $this->invoice_next_quantity;
|
||||
|
||||
foreach ($this->charges->filter(function($item) { return ! $item->processed; }) as $oo)
|
||||
{
|
||||
$o->addTaxes();
|
||||
$result->push($o);
|
||||
}
|
||||
|
||||
// Add additional charges
|
||||
foreach ($this->charges->filter(function($item) { return ! $item->processed; }) as $oo) {
|
||||
$o = new InvoiceItem;
|
||||
$o->active = TRUE;
|
||||
$o->service_id = $oo->service_id;
|
||||
|
77
database/factories/InvoiceItemFactory.php
Normal file
77
database/factories/InvoiceItemFactory.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
use Faker\Generator as Faker;
|
||||
use Leenooks\Carbon as Carbon;
|
||||
|
||||
$factory->define(App\Models\InvoiceItem::class, function (Faker $faker) {
|
||||
return [
|
||||
'id'=>1,
|
||||
];
|
||||
});
|
||||
|
||||
// Weekly
|
||||
$factory->state(App\Models\InvoiceItem::class,'week',[
|
||||
'date_start'=>Carbon::now()->startOfWeek(),
|
||||
'date_stop'=>Carbon::now()->endOfWeek(),
|
||||
]);
|
||||
|
||||
$factory->state(App\Models\InvoiceItem::class,'week-mid',[
|
||||
'date_start'=>Carbon::now()->startOfWeek(),
|
||||
'date_stop'=>Carbon::now()->endOfWeek()->addDays(3),
|
||||
]);
|
||||
|
||||
// Monthly
|
||||
$factory->state(App\Models\InvoiceItem::class,'month',[
|
||||
'date_start'=>Carbon::now()->startOfMonth(),
|
||||
'date_stop'=>Carbon::now()->endOfMonth(),
|
||||
]);
|
||||
|
||||
$factory->state(App\Models\InvoiceItem::class,'month-mid',[
|
||||
'date_start'=>Carbon::now()->startOfMonth(),
|
||||
'date_stop'=>Carbon::now()->endOfMonth()->addDays(Carbon::now()->daysInMonth/2+1),
|
||||
]);
|
||||
|
||||
// Quarterly
|
||||
$factory->state(App\Models\InvoiceItem::class,'quarter',[
|
||||
'date_start'=>Carbon::now()->startOfQuarter(),
|
||||
'date_stop'=>Carbon::now()->endOfQuarter(),
|
||||
]);
|
||||
|
||||
$factory->state(App\Models\InvoiceItem::class,'quarter-mid',[
|
||||
'date_start'=>Carbon::now()->startOfQuarter(),
|
||||
'date_stop'=>Carbon::now()->startOfQuarter()->addDays(45),
|
||||
]);
|
||||
|
||||
// Half Yearly
|
||||
$factory->state(App\Models\InvoiceItem::class,'half',[
|
||||
'date_start'=>Carbon::now()->startOfHalf(),
|
||||
'date_stop'=>Carbon::now()->endOfHalf(),
|
||||
]);
|
||||
|
||||
$factory->state(App\Models\InvoiceItem::class,'half-mid',[
|
||||
'date_start'=>Carbon::now()->startOfHalf(),
|
||||
'date_stop'=>Carbon::now()->startOfHalf()->addDays(90),
|
||||
]);
|
||||
|
||||
// Yearly
|
||||
$factory->state(App\Models\InvoiceItem::class,'year',[
|
||||
'date_start'=>Carbon::now()->startOfYear(),
|
||||
'date_stop'=>Carbon::now()->endOfYear(),
|
||||
]);
|
||||
|
||||
$factory->state(App\Models\InvoiceItem::class,'year-mid',[
|
||||
'date_start'=>Carbon::now()->startOfYear(),
|
||||
'date_stop'=>Carbon::now()->startOfYear()->addDays(181),
|
||||
]);
|
||||
|
||||
// Two Yearly (price_recurr_strict ignored)
|
||||
$factory->state(App\Models\InvoiceItem::class,'2year',[
|
||||
'date_start'=>Carbon::now()->subyear(),
|
||||
'date_stop'=>Carbon::now()->subday(),
|
||||
]);
|
||||
|
||||
// Three Yearly (price_recurr_strict ignored)
|
||||
$factory->state(App\Models\InvoiceItem::class,'3year',[
|
||||
'date_start'=>Carbon::now()->subyear(2),
|
||||
'date_stop'=>Carbon::now()->subday(),
|
||||
]);
|
19
database/factories/ProductFactory.php
Normal file
19
database/factories/ProductFactory.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Faker\Generator as Faker;
|
||||
|
||||
$factory->define(App\Models\Product::class, function (Faker $faker) {
|
||||
return [
|
||||
'id'=>1,
|
||||
];
|
||||
});
|
||||
|
||||
$factory->state(App\Models\Product::class,'active',[
|
||||
'active' => '1',
|
||||
]);
|
||||
$factory->state(App\Models\Product::class,'strict',[
|
||||
'price_recurr_strict' => '1',
|
||||
]);
|
||||
$factory->state(App\Models\Product::class,'notstrict',[
|
||||
'price_recurr_strict' => '0',
|
||||
]);
|
95
database/factories/ServiceFactory.php
Normal file
95
database/factories/ServiceFactory.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
use Faker\Generator as Faker;
|
||||
|
||||
$factory->define(App\Models\Service::class, function (Faker $faker) {
|
||||
return [
|
||||
'account_id'=>1,
|
||||
];
|
||||
});
|
||||
|
||||
$factory->afterMaking(App\Models\Service::class, function ($service,$faker) {
|
||||
$product = factory(App\Models\Product::class)->make();
|
||||
|
||||
$service->setRelation('product',$product);
|
||||
$service->product_id = $product->id;
|
||||
});
|
||||
|
||||
// Weekly
|
||||
$factory->afterMakingState(App\Models\Service::class,'week',function ($service,$faker) {
|
||||
$invoice_items = factory(App\Models\InvoiceItem::class,1)->state('week')->make();
|
||||
$service->setRelation('invoice_items',$invoice_items);
|
||||
$service->recur_schedule = 0;
|
||||
});
|
||||
|
||||
$factory->afterMakingState(App\Models\Service::class,'week-mid',function ($service,$faker) {
|
||||
$invoice_items = factory(App\Models\InvoiceItem::class,1)->state('week-mid')->make();
|
||||
$service->setRelation('invoice_items',$invoice_items);
|
||||
$service->recur_schedule = 0;
|
||||
});
|
||||
|
||||
// Monthly
|
||||
$factory->afterMakingState(App\Models\Service::class,'month',function ($service,$faker) {
|
||||
$invoice_items = factory(App\Models\InvoiceItem::class,1)->state('month')->make();
|
||||
$service->setRelation('invoice_items',$invoice_items);
|
||||
$service->recur_schedule = 1;
|
||||
});
|
||||
|
||||
$factory->afterMakingState(App\Models\Service::class,'month-mid',function ($service,$faker) {
|
||||
$invoice_items = factory(App\Models\InvoiceItem::class,1)->state('month-mid')->make();
|
||||
$service->setRelation('invoice_items',$invoice_items);
|
||||
$service->recur_schedule = 1;
|
||||
});
|
||||
|
||||
// Quarterly
|
||||
$factory->afterMakingState(App\Models\Service::class,'quarter',function ($service,$faker) {
|
||||
$invoice_items = factory(App\Models\InvoiceItem::class,1)->state('quarter')->make();
|
||||
$service->setRelation('invoice_items',$invoice_items);
|
||||
$service->recur_schedule = 2;
|
||||
});
|
||||
|
||||
$factory->afterMakingState(App\Models\Service::class,'quarter-mid',function ($service,$faker) {
|
||||
$invoice_items = factory(App\Models\InvoiceItem::class,1)->state('quarter-mid')->make();
|
||||
$service->setRelation('invoice_items',$invoice_items);
|
||||
$service->recur_schedule = 2;
|
||||
});
|
||||
|
||||
// Half Yearly
|
||||
$factory->afterMakingState(App\Models\Service::class,'half',function ($service,$faker) {
|
||||
$invoice_items = factory(App\Models\InvoiceItem::class,1)->state('half')->make();
|
||||
$service->setRelation('invoice_items',$invoice_items);
|
||||
$service->recur_schedule = 3;
|
||||
});
|
||||
|
||||
$factory->afterMakingState(App\Models\Service::class,'half-mid',function ($service,$faker) {
|
||||
$invoice_items = factory(App\Models\InvoiceItem::class,1)->state('half-mid')->make();
|
||||
$service->setRelation('invoice_items',$invoice_items);
|
||||
$service->recur_schedule = 3;
|
||||
});
|
||||
|
||||
// Yearly
|
||||
$factory->afterMakingState(App\Models\Service::class,'year',function ($service,$faker) {
|
||||
$invoice_items = factory(App\Models\InvoiceItem::class,1)->state('year')->make();
|
||||
$service->setRelation('invoice_items',$invoice_items);
|
||||
$service->recur_schedule = 4;
|
||||
});
|
||||
|
||||
$factory->afterMakingState(App\Models\Service::class,'year-mid',function ($service,$faker) {
|
||||
$invoice_items = factory(App\Models\InvoiceItem::class,1)->state('year-mid')->make();
|
||||
$service->setRelation('invoice_items',$invoice_items);
|
||||
$service->recur_schedule = 4;
|
||||
});
|
||||
|
||||
// 2 Yearly
|
||||
$factory->afterMakingState(App\Models\Service::class,'2year',function ($service,$faker) {
|
||||
$invoice_items = factory(App\Models\InvoiceItem::class,1)->state('2year')->make();
|
||||
$service->setRelation('invoice_items',$invoice_items);
|
||||
$service->recur_schedule = 5;
|
||||
});
|
||||
|
||||
// 3 Yearly
|
||||
$factory->afterMakingState(App\Models\Service::class,'3year',function ($service,$faker) {
|
||||
$invoice_items = factory(App\Models\InvoiceItem::class,1)->state('3year')->make();
|
||||
$service->setRelation('invoice_items',$invoice_items);
|
||||
$service->recur_schedule = 6;
|
||||
});
|
@ -1,19 +1,13 @@
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<strong>Next Invoice Details</strong>
|
||||
</div>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th colspan="3">{{ $o->name }}</th><th class="text-right">${{ number_format($o->next_invoice_items()->sum('total'),2) }}</th>
|
||||
</tr>
|
||||
|
||||
<div class="card-body">
|
||||
<table class="table table-borderless">
|
||||
<tr>
|
||||
<th colspan="3">{{ $o->name }}</th><th class="text-right">${{ number_format($o->next_invoice_items()->sum('total'),2) }}</th>
|
||||
</tr>
|
||||
|
||||
@foreach ($o->next_invoice_items() as $io)
|
||||
<tr>
|
||||
<td class="pt-0 pb-1"> </td><td class="pt-0 pb-1">{{ $io->item_type_name }}</td><td class="text-right pt-0 pb-1">${{ number_format($io->total,2) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@foreach ($o->next_invoice_items() as $io)
|
||||
<tr>
|
||||
<td class="pt-0 pb-1"> </td>
|
||||
<td class="pt-0 pb-1">{{ $io->item_type_name }}</td>
|
||||
<td class="text-right pt-0 pb-1">${{ number_format($io->total,2) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
@ -8,7 +8,7 @@
|
||||
@endsection
|
||||
|
||||
@section('contentheader_title')
|
||||
Service: {{ $o->sid }} <strong>NBN-50/20-100</strong>
|
||||
Service: {{ $o->sid }} <strong>{{ $o->product->name }}</strong>
|
||||
@endsection
|
||||
@section('contentheader_description')
|
||||
{{ $o->sname }}: {{ $o->sdesc }}
|
||||
@ -34,20 +34,22 @@
|
||||
<li class="nav-item"><a class="nav-link" href="#emails" data-toggle="tab">Emails</a></li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav nav-pills ml-auto p-2">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
Dropdown <span class="caret"></span>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item" tabindex="-1" href="#tab_3">Action</a>
|
||||
<a class="dropdown-item" tabindex="-1" href="#">Another action</a>
|
||||
<a class="dropdown-item" tabindex="-1" href="#">Something else here</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" tabindex="-1" href="#">Separated link</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@can('update',$o)
|
||||
<ul class="nav nav-pills ml-auto p-2">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
ACTION <span class="caret"></span>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item" tabindex="-1" href="#tab_3">Action</a>
|
||||
<a class="dropdown-item" tabindex="-1" href="#">Another action</a>
|
||||
<a class="dropdown-item" tabindex="-1" href="#">Something else here</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" tabindex="-1" href="#">Separated link</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@endcan
|
||||
</div><!-- /.card-header -->
|
||||
|
||||
<div class="card-body">
|
||||
@ -59,7 +61,7 @@
|
||||
Product.
|
||||
</div>
|
||||
<div class="tab-pane fade" id="invoice_next" role="tabpanel">
|
||||
Invoice Next.
|
||||
@include('common.service.widget.invoice')
|
||||
</div>
|
||||
<div class="tab-pane fade" id="invoices" role="tabpanel">
|
||||
Invoices.
|
||||
|
@ -1,11 +1,10 @@
|
||||
<div class="card">
|
||||
<!-- @todo -->
|
||||
@if($o->service->isPending())
|
||||
<div class="ribbon-wrapper ribbon-lg">
|
||||
<div class="ribbon bg-warning">
|
||||
Pending
|
||||
<div class="ribbon-wrapper ribbon-lg">
|
||||
<div class="ribbon bg-warning">
|
||||
Pending
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="card-header bg-gray-dark">
|
||||
|
@ -2,38 +2,53 @@
|
||||
<div class="card-header bg-light">
|
||||
<h3 class="card-title">Service Information</h3>
|
||||
</div>
|
||||
|
||||
<div class="card-body bg-light">
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<td>{{ $o->status }}</td>
|
||||
<td>{!! $o->status_html !!}</td>
|
||||
</tr>
|
||||
@if ($o->active)
|
||||
@if ($o->active or $o->isPending())
|
||||
<tr>
|
||||
<th>Billed</th>
|
||||
<td>{{ $o->billing_period }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Invoiced To</th>
|
||||
<td>{{ $o->invoice_to->format('Y-m-d') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Paid Until</th>
|
||||
<td>{{ $o->paid_to->format('Y-m-d') }}</td>
|
||||
</tr>
|
||||
@if($o->active)
|
||||
<tr>
|
||||
<th>Invoiced To</th>
|
||||
<td>{{ $o->invoice_to->format('Y-m-d') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Paid Until</th>
|
||||
<td>{{ $o->paid_to->format('Y-m-d') }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
<tr>
|
||||
<th>Next Invoice</th>
|
||||
<td>{{ $o->invoice_next->format('Y-m-d') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Estimated Invoice</th>
|
||||
<td>${{ number_format($o->billing_price,2) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Next Estimated Invoice</th>
|
||||
<td>${{ number_format($o->next_invoice_items()->sum('total'),2) }} <sup>*</sup></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Payment Method</th>
|
||||
<td>@if ($o->autopay)Direct Debit @else Invoice @endif</td>
|
||||
</tr>
|
||||
|
||||
@else
|
||||
<tr>
|
||||
<th>Cancelled</th>
|
||||
<td>{!! $o->date_end ? $o->date_end->format('Y-m-d') : $o->paid_to->format('Y-m-d').'<sup>*</sup>' !!}</td>
|
||||
</tr>
|
||||
@endif
|
||||
<tr>
|
||||
<th>Payment Method</th>
|
||||
<td>@if ($o->autopay)Direct Debit @else Invoice @endif</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if($o->active OR $o->isPending())
|
||||
<div class="card-footer sm">
|
||||
<strong><sup>*</sup>NOTE:</strong> Estimated Invoice does not include any setup, connection nor all current billing cycle usage charges.
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
@ -1,49 +0,0 @@
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Service Information</h3>
|
||||
<div class="box-tools pull-right">
|
||||
<button type="button" class="btn btn-box-tool" data-widget="collapse" data-toggle="tooltip" title="Collapse">
|
||||
<i class="fa fa-minus"></i></button>
|
||||
<button type="button" class="btn btn-box-tool" data-widget="remove" data-toggle="tooltip" title="Remove">
|
||||
<i class="fa fa-times"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-body">
|
||||
<table class="table table-condensed" width="100%">
|
||||
<tr>
|
||||
<th>Account</th><td>{{ $o->account->company }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Active</th><td>{{ $o->active }} ({{ $o->order_status }}) [{{ $o->status }}]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Type</th><td>{{ $o->type->type }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Product</th><td>{{ $o->product->name }}: {{ $o->name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Billing Period</th><td>{{ $o->recur_schedule }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Billing Amount</th><td>{{ $o->cost }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Last Invoice</th><td>{{ $o->date_last_invoice }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Paid Until</th><td>{{ 'TBA' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Next Invoice</th><td>{{ $o->date_next_invoice }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
{{--
|
||||
<div class="box-footer">
|
||||
</div>
|
||||
--}}
|
||||
</div>
|
@ -1,52 +0,0 @@
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">New Order <small>({{$o->order_status}})</small></h3>
|
||||
<div class="box-tools pull-right">
|
||||
<button type="button" class="btn btn-box-tool" data-widget="collapse" data-toggle="tooltip" title="Collapse">
|
||||
<i class="fa fa-minus"></i></button>
|
||||
<button type="button" class="btn btn-box-tool" data-widget="remove" data-toggle="tooltip" title="Remove">
|
||||
<i class="fa fa-times"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-body">
|
||||
<table class="table table-condensed" width="100%">
|
||||
<tr>
|
||||
<th>Account</th><td>{{ $o->account->company }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Product</th><td>{{ $o->product->name }}: {{ $o->name }}</td>
|
||||
</tr>
|
||||
@if($o->date_last_invoice)
|
||||
<tr>
|
||||
<th>Last Invoice</th><td>{{ $o->date_last_invoice }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Paid Until</th><td>{{ 'TBA' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Next Invoice</th><td>{{ $o->date_next_invoice }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
<tr>
|
||||
<th>Ordered</th><td>{{ $o->date_orig->format('Y-m-d') }}</td>
|
||||
</tr>
|
||||
@if ($o->date_last)
|
||||
<tr>
|
||||
<th>Update</th><td>{{ $o->date_last->format('Y-m-d') }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
<tr>
|
||||
<th>Order Details</th><td>{!! $o->order_info_details !!}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Reference:</th><td>{{ \Illuminate\Support\Arr::get($o->order_info,'order_reference','') }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{--
|
||||
<div class="box-footer">
|
||||
</div>
|
||||
--}}
|
||||
</div>
|
@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
class ExampleTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* A basic test example.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testBasicTest()
|
||||
{
|
||||
$response = $this->get('/login');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
}
|
100
tests/Feature/ServiceTest.php
Normal file
100
tests/Feature/ServiceTest.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Product;
|
||||
use App\Models\Service;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ServiceTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* A basic feature test example.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testBilling()
|
||||
{
|
||||
// Test Weekly Billing
|
||||
$o = factory(Service::class)->states('week')->make();
|
||||
$o->setRelation('product',factory(Product::class)->states('notstrict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Weekly Equals 1');
|
||||
$o->setRelation('product',factory(Product::class)->states('strict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Weekly Equals 1');
|
||||
|
||||
$o = factory(Service::class)->states('week-mid')->make();
|
||||
$o->setRelation('product',factory(Product::class)->states('notstrict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Weekly Mid Equals 0.57');
|
||||
$o->setRelation('product',factory(Product::class)->states('strict')->make());
|
||||
$this->assertEquals(0.57,$o->invoice_next_quantity,'Weekly Mid Equals 0.57');
|
||||
|
||||
// Test Monthly Billing
|
||||
$o = factory(Service::class)->states('month')->make();
|
||||
$o->setRelation('product',factory(Product::class)->states('notstrict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Monthly Equals 1');
|
||||
$o->setRelation('product',factory(Product::class)->states('strict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Monthly Equals 1');
|
||||
|
||||
$o = factory(Service::class)->states('month-mid')->make();
|
||||
$o->setRelation('product',factory(Product::class)->states('notstrict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Monthly Equals 1');
|
||||
$o->setRelation('product',factory(Product::class)->states('strict')->make());
|
||||
$this->assertEquals(round(round(Carbon::now()->addMonth()->daysInMonth/2,0)/Carbon::now()->addMonth()->daysInMonth,2),$o->invoice_next_quantity,'Monthly Mid Equals 0.5');
|
||||
|
||||
// Test Quarterly Billing
|
||||
$o = factory(Service::class)->states('quarter')->make();
|
||||
$o->setRelation('product',factory(Product::class)->states('notstrict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Quarterly Equals 1');
|
||||
$o->setRelation('product',factory(Product::class)->states('strict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Quarterly Equals 1');
|
||||
|
||||
$o = factory(Service::class)->states('quarter-mid')->make();
|
||||
$o->setRelation('product',factory(Product::class)->states('notstrict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Quarterly Equals 1');
|
||||
$o->setRelation('product',factory(Product::class)->states('strict')->make());
|
||||
$this->assertEqualsWithDelta(0.5,$o->invoice_next_quantity,.02,'Quarterly Mid Equals 0.5');
|
||||
|
||||
// Test Half Year Billing
|
||||
$o = factory(Service::class)->states('half')->make();
|
||||
$o->setRelation('product',factory(Product::class)->states('notstrict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Half Yearly Equals 1');
|
||||
$o->setRelation('product',factory(Product::class)->states('strict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Half Yearly Equals 1');
|
||||
|
||||
$o = factory(Service::class)->states('half-mid')->make();
|
||||
$o->setRelation('product',factory(Product::class)->states('notstrict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Half Yearly Equals 1');
|
||||
$o->setRelation('product',factory(Product::class)->states('strict')->make());
|
||||
$this->assertEquals(0.5,$o->invoice_next_quantity,'Half Yearly Mid Equals 0.5');
|
||||
|
||||
// Test Year Billing
|
||||
$o = factory(Service::class)->states('year')->make();
|
||||
$o->setRelation('product',factory(Product::class)->states('notstrict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Yearly Equals 1');
|
||||
$o->setRelation('product',factory(Product::class)->states('strict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Yearly Equals 1');
|
||||
|
||||
$o = factory(Service::class)->states('year-mid')->make();
|
||||
$o->setRelation('product',factory(Product::class)->states('notstrict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Yearly Equals 1');
|
||||
$o->setRelation('product',factory(Product::class)->states('strict')->make());
|
||||
$this->assertEquals(0.5,$o->invoice_next_quantity,'Yearly Mid Equals 0.5');
|
||||
|
||||
// Test 2 Year Billing (price_recurr_strict ignored)
|
||||
$o = factory(Service::class)->states('2year')->make();
|
||||
$o->setRelation('product',factory(Product::class)->states('notstrict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Two Yearly Equals 1');
|
||||
$o->setRelation('product',factory(Product::class)->states('strict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Two Yearly Equals 1');
|
||||
|
||||
// Test 3 Year Billing (price_recurr_strict ignored)
|
||||
$o = factory(Service::class)->states('3year')->make();
|
||||
$o->setRelation('product',factory(Product::class)->states('notstrict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Three Yearly Equals 1');
|
||||
$o->setRelation('product',factory(Product::class)->states('strict')->make());
|
||||
$this->assertEquals(1,$o->invoice_next_quantity,'Three Yearly Equals 1');
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
class ExampleTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* A basic test example.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testBasicTest()
|
||||
{
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user