Invoice rendering for service, and unit testing for Invoice Item quantity

This commit is contained in:
Deon George 2020-02-06 18:31:43 +09:00
parent 5f5d114f42
commit ebd4367975
14 changed files with 541 additions and 236 deletions

View File

@ -4,6 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Leenooks\Carbon;
class InvoiceItem extends Model class InvoiceItem extends Model
{ {
@ -36,6 +37,36 @@ class InvoiceItem extends Model
/** ATTRIBUTES **/ /** 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() public function getItemTypeNameAttribute()
{ {
$types = [ $types = [

View File

@ -2,15 +2,18 @@
namespace App\Models; 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\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use App\Traits\NextKey; use App\Traits\NextKey;
use Leenooks\Carbon;
class Service extends Model class Service extends Model
{ {
@ -23,8 +26,11 @@ class Service extends Model
protected $dates = [ protected $dates = [
'date_last_invoice', 'date_last_invoice',
'date_next_invoice' 'date_next_invoice'.
'date_start',
'date_end',
]; ];
public $dateFormat = 'U'; public $dateFormat = 'U';
protected $table = 'ab_service'; protected $table = 'ab_service';
@ -303,38 +309,124 @@ class Service extends Model
* Return the date for the next invoice * Return the date for the next invoice
* *
* @todo This function negates the need for date_next_invoice * @todo This function negates the need for date_next_invoice
* @return null * @return Carbon|string
*/ */
public function getInvoiceNextAttribute() public function getInvoiceNextAttribute()
{ {
$last = $this->getInvoiceToAttribute(); $last = $this->getInvoiceToAttribute();
$date = $last ? $last->addDay() : now(); $date = $last ? $last->addDay() : Carbon::now();
return request()->wantsJson() ? $date->format('Y-m-d') : $date; return request()->wantsJson() ? $date->format('Y-m-d') : $date;
} }
/**
* Return the end date for the next invoice
*
* @return mixed
* @throws Exception
*/
public function getInvoiceNextEndAttribute() public function getInvoiceNextEndAttribute()
{ {
switch ($this->recur_schedule) switch ($this->recur_schedule) {
{
// Weekly // 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 // 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 // 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 // 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 // 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 // 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 // Three Yearly
case 6: $date = $this->getInvoiceNextAttribute()->addYear(3); break; // NOTE: price_recurr_strict ignored
default: throw new \Exception('Unknown recur_schedule'); 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 public function getStatusHTMLAttribute(): string
{ {
$class = NULL; $class = NULL;
if ($this->isPending())
$class = 'badge-warning';
else
switch ($this->status) switch ($this->status)
{ {
case 'ACTIVE': case 'ACTIVE':
$class = 'badge-success'; $class = 'badge-success';
break; break;
case 'INACTIVE':
$class = 'badge-danger';
break;
} }
return sprintf('<span class="badge %s">%s</span>',$class,$this->status); 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); return round($value*1.1,2);
} }
public function invoices_due(): Collection public function invoices_due(): DatabaseCollection
{ {
$this->load('invoice_items.invoice'); $this->load('invoice_items.invoice');
@ -635,29 +737,41 @@ class Service extends Model
*/ */
public function isPending(): bool 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(); $result = collect();
$o = new InvoiceItem; $o = new InvoiceItem;
// If the service is active, there will be service charges
if ($this->active or $this->isPending()) {
$o->active = TRUE; $o->active = TRUE;
$o->service_id = $this->id; $o->service_id = $this->id;
$o->product_id = $this->product_id; $o->product_id = $this->product_id;
$o->quantity = 1;
$o->item_type = 0; $o->item_type = 0;
$o->price_base = $this->price ?: $this->product->price($this->recur_schedule); // @todo change to a method in this class $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->recurring_schedule = $this->recur_schedule;
$o->date_start = $this->invoice_next; $o->date_start = $this->invoice_next;
$o->date_stop = $this->invoice_next_end; $o->date_stop = $this->invoice_next_end;
$o->quantity = $this->invoice_next_quantity;
$o->addTaxes(); $o->addTaxes();
$result->push($o); $result->push($o);
}
foreach ($this->charges->filter(function($item) { return ! $item->processed; }) as $oo) // Add additional charges
{ foreach ($this->charges->filter(function($item) { return ! $item->processed; }) as $oo) {
$o = new InvoiceItem; $o = new InvoiceItem;
$o->active = TRUE; $o->active = TRUE;
$o->service_id = $oo->service_id; $o->service_id = $oo->service_id;

View 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(),
]);

View 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',
]);

View 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;
});

View File

@ -1,19 +1,13 @@
<div class="card"> <table class="table">
<div class="card-header">
<strong>Next Invoice Details</strong>
</div>
<div class="card-body">
<table class="table table-borderless">
<tr> <tr>
<th colspan="3">{{ $o->name }}</th><th class="text-right">${{ number_format($o->next_invoice_items()->sum('total'),2) }}</th> <th colspan="3">{{ $o->name }}</th><th class="text-right">${{ number_format($o->next_invoice_items()->sum('total'),2) }}</th>
</tr> </tr>
@foreach ($o->next_invoice_items() as $io) @foreach ($o->next_invoice_items() as $io)
<tr> <tr>
<td class="pt-0 pb-1">&nbsp;</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> <td class="pt-0 pb-1">&nbsp;</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> </tr>
@endforeach @endforeach
</table> </table>
</div>
</div>

View File

@ -8,7 +8,7 @@
@endsection @endsection
@section('contentheader_title') @section('contentheader_title')
Service: {{ $o->sid }} <strong>NBN-50/20-100</strong> Service: {{ $o->sid }} <strong>{{ $o->product->name }}</strong>
@endsection @endsection
@section('contentheader_description') @section('contentheader_description')
{{ $o->sname }}: {{ $o->sdesc }} {{ $o->sname }}: {{ $o->sdesc }}
@ -34,10 +34,11 @@
<li class="nav-item"><a class="nav-link" href="#emails" data-toggle="tab">Emails</a></li> <li class="nav-item"><a class="nav-link" href="#emails" data-toggle="tab">Emails</a></li>
</ul> </ul>
@can('update',$o)
<ul class="nav nav-pills ml-auto p-2"> <ul class="nav nav-pills ml-auto p-2">
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#"> <a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#">
Dropdown <span class="caret"></span> ACTION <span class="caret"></span>
</a> </a>
<div class="dropdown-menu dropdown-menu-right"> <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="#tab_3">Action</a>
@ -48,6 +49,7 @@
</div> </div>
</li> </li>
</ul> </ul>
@endcan
</div><!-- /.card-header --> </div><!-- /.card-header -->
<div class="card-body"> <div class="card-body">
@ -59,7 +61,7 @@
Product. Product.
</div> </div>
<div class="tab-pane fade" id="invoice_next" role="tabpanel"> <div class="tab-pane fade" id="invoice_next" role="tabpanel">
Invoice Next. @include('common.service.widget.invoice')
</div> </div>
<div class="tab-pane fade" id="invoices" role="tabpanel"> <div class="tab-pane fade" id="invoices" role="tabpanel">
Invoices. Invoices.

View File

@ -1,5 +1,4 @@
<div class="card"> <div class="card">
<!-- @todo -->
@if($o->service->isPending()) @if($o->service->isPending())
<div class="ribbon-wrapper ribbon-lg"> <div class="ribbon-wrapper ribbon-lg">
<div class="ribbon bg-warning"> <div class="ribbon bg-warning">

View File

@ -2,17 +2,19 @@
<div class="card-header bg-light"> <div class="card-header bg-light">
<h3 class="card-title">Service Information</h3> <h3 class="card-title">Service Information</h3>
</div> </div>
<div class="card-body bg-light"> <div class="card-body bg-light">
<table class="table table-sm"> <table class="table table-sm">
<tr> <tr>
<th>Status</th> <th>Status</th>
<td>{{ $o->status }}</td> <td>{!! $o->status_html !!}</td>
</tr> </tr>
@if ($o->active) @if ($o->active or $o->isPending())
<tr> <tr>
<th>Billed</th> <th>Billed</th>
<td>{{ $o->billing_period }}</td> <td>{{ $o->billing_period }}</td>
</tr> </tr>
@if($o->active)
<tr> <tr>
<th>Invoiced To</th> <th>Invoiced To</th>
<td>{{ $o->invoice_to->format('Y-m-d') }}</td> <td>{{ $o->invoice_to->format('Y-m-d') }}</td>
@ -21,19 +23,32 @@
<th>Paid Until</th> <th>Paid Until</th>
<td>{{ $o->paid_to->format('Y-m-d') }}</td> <td>{{ $o->paid_to->format('Y-m-d') }}</td>
</tr> </tr>
@endif
<tr> <tr>
<th>Next Invoice</th> <th>Next Invoice</th>
<td>{{ $o->invoice_next->format('Y-m-d') }}</td> <td>{{ $o->invoice_next->format('Y-m-d') }}</td>
</tr> </tr>
<tr> <tr>
<th>Estimated Invoice</th> <th>Next Estimated Invoice</th>
<td>${{ number_format($o->billing_price,2) }}</td> <td>${{ number_format($o->next_invoice_items()->sum('total'),2) }} <sup>*</sup></td>
</tr> </tr>
@endif
<tr> <tr>
<th>Payment Method</th> <th>Payment Method</th>
<td>@if ($o->autopay)Direct Debit @else Invoice @endif</td> <td>@if ($o->autopay)Direct Debit @else Invoice @endif</td>
</tr> </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
</table> </table>
</div> </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> </div>

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
}

View 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');
}
}

View File

@ -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);
}
}