17 changed files with 1041 additions and 0 deletions
-
17app/Entities/Invoices/Invoice.php
-
133app/Http/Controllers/InvoiceDraftController.php
-
14app/Http/Controllers/InvoicesController.php
-
113app/Services/InvoiceDraft/InvoiceDraft.php
-
150app/Services/InvoiceDraft/InvoiceDraftCollection.php
-
30app/Services/InvoiceDraft/Item.php
-
38database/migrations/2017_10_05_162758_create_invoices_table.php
-
31resources/lang/id/invoice.php
-
26resources/views/invoices/create.blade.php
-
59resources/views/invoices/partials/draft-confirm.blade.php
-
62resources/views/invoices/partials/draft-item-list.blade.php
-
6resources/views/invoices/partials/form-draft-detail.blade.php
-
17resources/views/invoices/partials/invoice-draft-tabs.blade.php
-
1routes/web.php
-
23routes/web/invoices.php
-
169tests/Feature/InvoiceEntryTest.php
-
152tests/Unit/InvoiceDraftCollectionTest.php
@ -0,0 +1,17 @@ |
|||
<?php |
|||
|
|||
namespace App\Entities\Invoices; |
|||
|
|||
use Illuminate\Database\Eloquent\Model; |
|||
|
|||
class Invoice extends Model |
|||
{ |
|||
protected $guarded = ['id','created_at','updated_at']; |
|||
|
|||
protected $casts = ['items' => 'array']; |
|||
|
|||
public function getRouteKeyName() |
|||
{ |
|||
return 'invoice_no'; |
|||
} |
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Entities\Projects\Project; |
|||
use App\Services\InvoiceDrafts\InvoiceDraft; |
|||
use App\Services\InvoiceDrafts\InvoiceDraftCollection; |
|||
use App\Services\InvoiceDrafts\Item; |
|||
use Illuminate\Http\Request; |
|||
|
|||
class InvoiceDraftController extends Controller |
|||
{ |
|||
private $draftCollection; |
|||
|
|||
public function __construct() |
|||
{ |
|||
$this->draftCollection = new InvoiceDraftCollection(); |
|||
} |
|||
|
|||
public function index(Request $request) |
|||
{ |
|||
$draft = $this->draftCollection->content()->first(); |
|||
$projects = Project::pluck('name', 'id'); |
|||
|
|||
return view('invoices.create', compact('draft', 'projects')); |
|||
} |
|||
|
|||
public function show(Request $request, $draftKey = null) |
|||
{ |
|||
$draft = $draftKey ? $this->draftCollection->get($draftKey) : $this->draftCollection->content()->first(); |
|||
if (is_null($draft)) { |
|||
flash(trans('invoice.draft_not_found'), 'danger'); |
|||
|
|||
return redirect()->route('invoices.create'); |
|||
} |
|||
|
|||
$projects = Project::pluck('name', 'id'); |
|||
return view('invoices.create', compact('draft', 'projects')); |
|||
} |
|||
|
|||
public function add(Request $request) |
|||
{ |
|||
$this->draftCollection->add(new InvoiceDraft()); |
|||
|
|||
return redirect()->route('invoices.create', $this->draftCollection->content()->last()->draftKey); |
|||
} |
|||
|
|||
public function addDraftItem(Request $request, $draftKey) |
|||
{ |
|||
$item = new Item(['description' => $request->description, 'amount' => $request->amount]); |
|||
$this->draftCollection->addItemToDraft($draftKey, $item); |
|||
|
|||
flash(trans('invoice.item_added')); |
|||
|
|||
return back(); |
|||
} |
|||
|
|||
public function updateDraftItem(Request $request, $draftKey) |
|||
{ |
|||
$this->draftCollection->updateDraftItem($draftKey, $request->item_key, $request->only('description', 'amount')); |
|||
|
|||
return back(); |
|||
} |
|||
|
|||
public function removeDraftItem(Request $request, $draftKey) |
|||
{ |
|||
$this->draftCollection->removeItemFromDraft($draftKey, $request->item_index); |
|||
|
|||
return back(); |
|||
} |
|||
|
|||
public function empty($draftKey) |
|||
{ |
|||
$this->draftCollection->emptyDraft($draftKey); |
|||
|
|||
return redirect()->route('invoices.create', $draftKey); |
|||
} |
|||
|
|||
public function remove(Request $request) |
|||
{ |
|||
$this->draftCollection->removeDraft($request->draft_key); |
|||
|
|||
if ($this->draftCollection->isEmpty()) { |
|||
return redirect()->route('invoices.create-empty'); |
|||
} |
|||
|
|||
$lastDraft = $this->draftCollection->content()->last(); |
|||
|
|||
return redirect()->route('invoices.create', $lastDraft->draftKey); |
|||
} |
|||
|
|||
public function destroy() |
|||
{ |
|||
$this->draftCollection->destroy(); |
|||
flash(trans('invoice.draft_destroyed'), 'warning'); |
|||
|
|||
return redirect()->route('cart.index'); |
|||
} |
|||
|
|||
public function proccess(Request $request, $draftKey) |
|||
{ |
|||
$this->validate($request, [ |
|||
'project_id' => 'required|exists:projects,id', |
|||
'notes' => 'nullable|string|max:100', |
|||
]); |
|||
|
|||
$draft = $this->draftCollection->updateDraftAttributes($draftKey, $request->only('project_id', 'notes')); |
|||
|
|||
if ($draft->getItemsCount() == 0) { |
|||
flash(trans('invoice.item_list_empty'), 'warning')->important(); |
|||
|
|||
return redirect()->route('invoices.create', [$draftKey]); |
|||
} |
|||
|
|||
flash(trans('invoice.confirm_instruction', ['back_link' => link_to_route('invoices.create', trans('app.back'), $draftKey)]), 'warning')->important(); |
|||
|
|||
return redirect()->route('invoices.create', [$draftKey, 'action' => 'confirm']); |
|||
} |
|||
|
|||
public function store(Request $request, $draftKey) |
|||
{ |
|||
$draft = $this->draftCollection->get($draftKey); |
|||
if (is_null($draft)) { |
|||
return redirect()->route('cart.index'); |
|||
} |
|||
|
|||
$invoice = $draft->store(); |
|||
$draft->destroy(); |
|||
flash(trans('invoice.created', ['invoice_no' => $invoice->invoice_no]), 'success')->important(); |
|||
|
|||
return redirect()->route('invoices.show', $invoice->invoice_no); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Entities\Invoices\Invoice; |
|||
use Illuminate\Http\Request; |
|||
|
|||
class InvoicesController extends Controller |
|||
{ |
|||
public function show(Invoice $invoice) |
|||
{ |
|||
return $invoice; |
|||
} |
|||
} |
|||
@ -0,0 +1,113 @@ |
|||
<?php |
|||
|
|||
namespace App\Services\InvoiceDrafts; |
|||
|
|||
use App\Entities\Invoices\Invoice; |
|||
|
|||
/** |
|||
* Invoice Draft. |
|||
*/ |
|||
class InvoiceDraft |
|||
{ |
|||
public $items = []; |
|||
|
|||
public $projectId; |
|||
public $notes; |
|||
|
|||
public function items() |
|||
{ |
|||
return collect($this->items)->sortBy('name'); |
|||
} |
|||
|
|||
public function addItem(Item $item) |
|||
{ |
|||
$this->items[] = $item; |
|||
|
|||
return $item; |
|||
} |
|||
|
|||
public function removeItem($itemKey) |
|||
{ |
|||
unset($this->items[$itemKey]); |
|||
} |
|||
|
|||
public function empty() |
|||
{ |
|||
$this->items = []; |
|||
} |
|||
|
|||
public function getTotal() |
|||
{ |
|||
return $this->items()->sum('amount'); |
|||
} |
|||
|
|||
public function getItemsCount() |
|||
{ |
|||
return $this->items()->count(); |
|||
} |
|||
|
|||
public function updateItem($itemKey, $newItemData) |
|||
{ |
|||
if (!isset($this->items[$itemKey])) { |
|||
return; |
|||
} |
|||
|
|||
$item = $this->items[$itemKey]; |
|||
|
|||
$this->items[$itemKey] = $item->updateAttribute($newItemData); |
|||
|
|||
return $item; |
|||
} |
|||
|
|||
public function store() |
|||
{ |
|||
$invoice = new Invoice(); |
|||
$invoice->invoice_no = $this->getNewInvoiceNo(); |
|||
$invoice->items = $this->getItemsArray(); |
|||
$invoice->project_id = $this->projectId; |
|||
$invoice->amount = $this->getTotal(); |
|||
$invoice->notes = $this->notes; |
|||
$invoice->status_id = 1; |
|||
$invoice->user_id = auth()->id() ?: 1; |
|||
|
|||
$invoice->save(); |
|||
|
|||
return $invoice; |
|||
} |
|||
|
|||
public function getNewInvoiceNo() |
|||
{ |
|||
$prefix = date('ym'); |
|||
|
|||
$lastInvoice = Invoice::orderBy('invoice_no', 'desc')->first(); |
|||
|
|||
if (!is_null($lastInvoice)) { |
|||
$lastInvoiceNo = $lastInvoice->invoice_no; |
|||
if (substr($lastInvoiceNo, 0, 4) == $prefix) { |
|||
return ++$lastInvoiceNo; |
|||
} |
|||
} |
|||
|
|||
return $prefix.'0001'; |
|||
} |
|||
|
|||
protected function getItemsArray() |
|||
{ |
|||
$items = []; |
|||
foreach ($this->items as $item) { |
|||
$items[] = [ |
|||
'description' => $item->description, |
|||
'amount' => $item->amount, |
|||
]; |
|||
} |
|||
|
|||
return $items; |
|||
} |
|||
|
|||
public function destroy() |
|||
{ |
|||
$cart = app(InvoiceDraftCollection::class); |
|||
|
|||
return $cart->removeDraft($this->draftKey); |
|||
} |
|||
} |
|||
@ -0,0 +1,150 @@ |
|||
<?php |
|||
|
|||
namespace App\Services\InvoiceDrafts; |
|||
|
|||
use App\Product; |
|||
use Illuminate\Support\Collection; |
|||
|
|||
/** |
|||
* InvoiceDraft Collection Class. |
|||
*/ |
|||
class InvoiceDraftCollection |
|||
{ |
|||
private $instance; |
|||
private $session; |
|||
|
|||
public function __construct() |
|||
{ |
|||
$this->session = session(); |
|||
$this->instance('drafts'); |
|||
} |
|||
|
|||
public function instance($instance = null) |
|||
{ |
|||
$instance = $instance ?: 'drafts'; |
|||
|
|||
$this->instance = sprintf('%s.%s', 'invoices', $instance); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function currentInstance() |
|||
{ |
|||
return str_replace('invoices.', '', $this->instance); |
|||
} |
|||
|
|||
public function add(InvoiceDraft $draft) |
|||
{ |
|||
$content = $this->getContent(); |
|||
$draft->draftKey = str_random(10); |
|||
$content->put($draft->draftKey, $draft); |
|||
|
|||
$this->session->put($this->instance, $content); |
|||
|
|||
return $draft; |
|||
} |
|||
|
|||
public function get($draftKey) |
|||
{ |
|||
$content = $this->getContent(); |
|||
if (isset($content[$draftKey])) { |
|||
return $content[$draftKey]; |
|||
} |
|||
} |
|||
|
|||
public function updateDraftAttributes($draftKey, $draftAttributes) |
|||
{ |
|||
$content = $this->getContent(); |
|||
|
|||
$content[$draftKey]->projectId = $draftAttributes['project_id']; |
|||
$content[$draftKey]->notes = $draftAttributes['notes']; |
|||
|
|||
$this->session->put($this->instance, $content); |
|||
|
|||
return $content[$draftKey]; |
|||
} |
|||
|
|||
public function emptyDraft($draftKey) |
|||
{ |
|||
$content = $this->getContent(); |
|||
$content[$draftKey]->empty(); |
|||
$this->session->put($this->instance, $content); |
|||
} |
|||
|
|||
public function removeDraft($draftKey) |
|||
{ |
|||
$content = $this->getContent(); |
|||
$content->pull($draftKey); |
|||
$this->session->put($this->instance, $content); |
|||
} |
|||
|
|||
public function content() |
|||
{ |
|||
return $this->getContent(); |
|||
} |
|||
|
|||
protected function getContent() |
|||
{ |
|||
$content = $this->session->has($this->instance) ? $this->session->get($this->instance) : collect([]); |
|||
|
|||
return $content; |
|||
} |
|||
|
|||
public function keys() |
|||
{ |
|||
return $this->getContent()->keys(); |
|||
} |
|||
|
|||
public function destroy() |
|||
{ |
|||
$this->session->remove($this->instance); |
|||
} |
|||
|
|||
public function addItemToDraft($draftKey, Item $item) |
|||
{ |
|||
$content = $this->getContent(); |
|||
$content[$draftKey]->addItem($item); |
|||
|
|||
$this->session->put($this->instance, $content); |
|||
|
|||
return $item; |
|||
} |
|||
|
|||
public function draftHasItem(TrasactionDraft $draft, Product $product) |
|||
{ |
|||
$item = $draft->search($product); |
|||
|
|||
return !is_null($item); |
|||
} |
|||
|
|||
public function updateDraftItem($draftKey, $itemKey, $newItemData) |
|||
{ |
|||
$content = $this->getContent(); |
|||
$content[$draftKey]->updateItem($itemKey, $newItemData); |
|||
|
|||
$this->session->put($this->instance, $content); |
|||
} |
|||
|
|||
public function removeItemFromDraft($draftKey, $itemKey) |
|||
{ |
|||
$content = $this->getContent(); |
|||
$content[$draftKey]->removeItem($itemKey); |
|||
|
|||
$this->session->put($this->instance, $content); |
|||
} |
|||
|
|||
public function count() |
|||
{ |
|||
return $this->getContent()->count(); |
|||
} |
|||
|
|||
public function isEmpty() |
|||
{ |
|||
return $this->count() == 0; |
|||
} |
|||
|
|||
public function hasContent() |
|||
{ |
|||
return !$this->isEmpty(); |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
<?php |
|||
|
|||
namespace App\Services\InvoiceDrafts; |
|||
|
|||
/** |
|||
* Draft Item class. |
|||
*/ |
|||
class Item |
|||
{ |
|||
public $description; |
|||
public $amount; |
|||
|
|||
public function __construct(array $itemDetail) |
|||
{ |
|||
$this->description = $itemDetail['description']; |
|||
$this->amount = $itemDetail['amount']; |
|||
} |
|||
|
|||
public function updateAttribute(array $newItemData) |
|||
{ |
|||
if (isset($newItemData['description'])) { |
|||
$this->description = $newItemData['description']; |
|||
} |
|||
if (isset($newItemData['amount'])) { |
|||
$this->amount = $newItemData['amount']; |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
<?php |
|||
|
|||
use Illuminate\Support\Facades\Schema; |
|||
use Illuminate\Database\Schema\Blueprint; |
|||
use Illuminate\Database\Migrations\Migration; |
|||
|
|||
class CreateInvoicesTable extends Migration |
|||
{ |
|||
/** |
|||
* Run the migrations. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function up() |
|||
{ |
|||
Schema::create('invoices', function (Blueprint $table) { |
|||
$table->increments('id'); |
|||
$table->unsignedInteger('project_id'); |
|||
$table->string('invoice_no', 8); |
|||
$table->text('items'); |
|||
$table->unsignedInteger('amount'); |
|||
$table->string('notes'); |
|||
$table->unsignedTinyInteger('status_id'); |
|||
$table->unsignedInteger('user_id'); |
|||
$table->timestamps(); |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Reverse the migrations. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function down() |
|||
{ |
|||
Schema::dropIfExists('invoices'); |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
<?php |
|||
|
|||
return [ |
|||
// Labels
|
|||
'invoice' => 'Invoice', |
|||
'list' => 'Daftar Invoice', |
|||
'search' => 'Cari Invoice', |
|||
'detail' => 'Detail Invoice', |
|||
'not_found' => 'Invoice tidak ditemukan', |
|||
'empty' => 'Belum ada Invoice', |
|||
'back_to_index' => 'Kembali ke daftar Invoice', |
|||
|
|||
// Actions
|
|||
'proccess' => 'Proses Invoice', |
|||
'create' => 'Input Invoice Baru', |
|||
'created' => 'Input Invoice baru telah berhasil.', |
|||
'show' => 'Detail Invoice', |
|||
'edit' => 'Edit Invoice', |
|||
'update' => 'Update Invoice', |
|||
'updated' => 'Update data Invoice telah berhasil.', |
|||
'delete' => 'Hapus Invoice', |
|||
'delete_confirm' => 'Anda yakin akan menghapus Invoice ini?', |
|||
'deleted' => 'Hapus data Invoice telah berhasil.', |
|||
'undeleted' => 'Data Invoice gagal dihapus.', |
|||
'undeleteable' => 'Data Invoice tidak dapat dihapus.', |
|||
|
|||
// Attributes
|
|||
'project' => 'Project', |
|||
'items' => 'Item Invoice', |
|||
'notes' => 'Catatan', |
|||
]; |
|||
@ -0,0 +1,26 @@ |
|||
@extends('layouts.app') |
|||
|
|||
@section('title', 'Entry Invoice') |
|||
|
|||
@section('content') |
|||
<div class="pull-right"> |
|||
<form action="{{ route('invoices.add') }}" method="POST"> |
|||
{{ csrf_field() }} |
|||
<input type="submit" class="btn btn-default navbar-btn" name="create-invoice-draft" id="invoice-draft-create-button" value="{{ trans('invoice.create') }}"> |
|||
</form> |
|||
</div> |
|||
<h1 class="page-header">{{ trans('invoice.list') }}</h1> |
|||
|
|||
<?php use Facades\App\Services\InvoiceDrafts\InvoiceDraftCollection; ?>
|
|||
@includeWhen(! InvoiceDraftCollection::isEmpty(), 'invoices.partials.invoice-draft-tabs') |
|||
@if ($draft) |
|||
@if (Request::get('action') == 'confirm') |
|||
@include('invoices.partials.draft-confirm') |
|||
@else |
|||
<div class="row"> |
|||
<div class="col-md-9">@include('invoices.partials.draft-item-list')</div> |
|||
<div class="col-md-3">@include('invoices.partials.form-draft-detail')</div> |
|||
</div> |
|||
@endif |
|||
@endif |
|||
@endsection |
|||
@ -0,0 +1,59 @@ |
|||
<div class="row"> |
|||
<div class="col-md-8"> |
|||
<div class="panel panel-default"> |
|||
<div class="panel-heading"><h3 class="panel-title">{{ trans('invoice.confirm') }}</h3></div> |
|||
<div class="panel-body"> |
|||
<table class="table"> |
|||
<thead> |
|||
<tr> |
|||
<th>#</th>
|
|||
<th>Deskripsi</th> |
|||
<th class="text-right">Biaya</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
@forelse($draft->items() as $key => $item) |
|||
<tr> |
|||
<td>{{ $key + 1 }}</td> |
|||
<td>{{ $item->description }}</td> |
|||
<td class="text-right">{{ formatRp($item->amount) }}</td> |
|||
</tr> |
|||
@empty |
|||
@endforelse |
|||
</tbody> |
|||
<tfoot> |
|||
<tr> |
|||
<th colspan="5" class="text-right">{{ trans('invoice.total') }} :</th> |
|||
<th class="text-right">{{ formatRp($draft->getTotal()) }}</th> |
|||
</tr> |
|||
</tfoot> |
|||
</table> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="col-md-4"> |
|||
<div class="panel panel-default"> |
|||
<div class="panel-heading"><h3 class="panel-title">{{ trans('invoice.detail') }}</h3></div> |
|||
<div class="panel-body"> |
|||
<table class="table table-condensed"> |
|||
<tbody> |
|||
<tr> |
|||
<td>{{ trans('invoice.project') }}</td> |
|||
<td> |
|||
{{ App\Entities\Projects\Project::findOrFail($draft->projectId)->name }} |
|||
</td> |
|||
</tr> |
|||
<tr><td>{{ trans('invoice.total') }}</td><th class="text-right">{{ formatRp($draft->getTotal()) }}</th></tr> |
|||
<tr><td>{{ trans('invoice.notes') }}</td><td>{{ $draft->notes }}</td></tr> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
<div class="panel-footer"> |
|||
{{ Form::open(['route' => ['invoices.store', $draft->draftKey]]) }} |
|||
{{ Form::submit(trans('invoice.save'), ['id' => 'save-invoice-draft', 'class' => 'btn btn-success']) }} |
|||
{{ link_to_route('invoices.create', trans('app.back'), $draft->draftKey, ['class' => 'btn btn-default']) }} |
|||
{{ Form::close() }} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,62 @@ |
|||
<legend> |
|||
{{ trans('invoice.items') }} |
|||
<small class="text-muted"> |
|||
({{ $draft->getItemsCount() }} Item) |
|||
</small> |
|||
</legend> |
|||
|
|||
<div class="panel panel-default"> |
|||
<div class="panel-body table-responsive"> |
|||
<table class="table"> |
|||
<thead> |
|||
<tr> |
|||
<th>#</th>
|
|||
<th>Deskripsi</th> |
|||
<th>Biaya</th> |
|||
<th class="text-center">Action</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<?php $no = 1 ?>
|
|||
@forelse($draft->items() as $key => $item) |
|||
<tr> |
|||
<td>{{ $no }} <?php $no++ ?></td>
|
|||
{{ Form::open(['route' => ['cart.update-draft-item', $draft->draftKey], 'method' => 'patch']) }} |
|||
{{ Form::hidden('item_key', $key) }} |
|||
<td>{{ Form::text('description', $item->description, ['id' => 'description-' . $key]) }}</td> |
|||
<td> |
|||
{{ Form::number('amount', $item->amount, ['id' => 'amount-' . $key]) }} |
|||
</td> |
|||
{{ Form::submit('update-item-' . $key, ['style'=>'display:none']) }} |
|||
{{ Form::close() }} |
|||
<td class="text-center show-on-hover-parent"> |
|||
{!! FormField::delete([ |
|||
'route' => ['cart.remove-draft-item', $draft->draftKey], |
|||
'onsubmit' => 'Yakin ingin menghapus Item ini?', |
|||
'class' => '', |
|||
], 'x', ['id' => 'remove-item-' . $key, 'class' => 'btn btn-danger btn-xs show-on-hover','title' => 'Hapus item ini'], ['item_index' => $key]) !!} |
|||
</td> |
|||
</tr> |
|||
@empty |
|||
@endforelse |
|||
<tr> |
|||
<td></td> |
|||
{{ Form::open(['route' => ['cart.add-draft-item', $draft->draftKey]]) }} |
|||
<td>{{ Form::text('description', null, ['id' => 'description']) }}</td> |
|||
<td> |
|||
{{ Form::number('amount', null, ['id' => 'amount']) }} |
|||
</td> |
|||
<td class="text-center">{{ Form::submit('add-item', ['class' => 'btn btn-primary']) }}</td> |
|||
{{ Form::close() }} |
|||
</tr> |
|||
</tbody> |
|||
<tfoot> |
|||
<tr> |
|||
<th colspan="2" class="text-right">{{ trans('invoice.amount') }} :</th> |
|||
<th class="text-right">{{ formatRp($draft->getTotal()) }}</th> |
|||
<th></th> |
|||
</tr> |
|||
</tfoot> |
|||
</table> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,6 @@ |
|||
<legend>{{ trans('invoice.detail') }}</legend> |
|||
{{ Form::open(['route' => ['cart.draft-proccess', $draft->draftKey], 'method' => 'patch']) }} |
|||
{!! FormField::select('project_id', $projects, ['label' => trans('invoice.project'), 'required' => true]) !!} |
|||
{!! FormField::textarea('notes', ['label' => trans('invoice.notes'), 'value' => $draft->notes]) !!} |
|||
{{ Form::submit(trans('invoice.proccess'), ['class' => 'btn btn-info']) }} |
|||
{{ Form::close() }} |
|||
@ -0,0 +1,17 @@ |
|||
<?php use Facades\App\Services\InvoiceDrafts\InvoiceDraftCollection; ?>
|
|||
<ul class="nav nav-tabs transaction-draft-tabs"> |
|||
@foreach(InvoiceDraftCollection::content() as $key => $content) |
|||
<?php $active = ($draft->draftKey == $key) ? 'class=active' : '' ?>
|
|||
<li {{ $active }} role="presentation"> |
|||
<a href="{{ route('invoices.create', $key) }}"> |
|||
{{ trans('invoice.invoice') }} - {{ $key }} |
|||
<form action="{{ route('cart.remove') }}" method="post" style="display:inline" onsubmit="return confirm('Yakin ingin menghapus Draft Transaksi ini?')"> |
|||
{{ csrf_field() }} |
|||
{{ method_field('delete') }} |
|||
<input type="hidden" name="draft_key" value="{{ $key }}"> |
|||
<input type="submit" value="x" style="margin: -2px -7px 0px 0px" class="btn-link btn-xs pull-right"> |
|||
</form> |
|||
</a> |
|||
</li> |
|||
@endforeach |
|||
</ul><!-- Tab panes --> |
|||
@ -0,0 +1,23 @@ |
|||
<?php |
|||
|
|||
Route::group(['middleware' => ['web','role:admin']], function() { |
|||
/* |
|||
* Invoice Draft Routes |
|||
*/ |
|||
Route::get('invoices/create', 'InvoiceDraftController@index')->name('invoices.create-empty'); |
|||
Route::get('invoices/create/{draftKey?}', 'InvoiceDraftController@show')->name('invoices.create'); |
|||
Route::post('invoices/create/{draftKey}', 'InvoiceDraftController@store')->name('invoices.store'); |
|||
Route::post('invoices/add-draft', 'InvoiceDraftController@add')->name('invoices.add'); |
|||
Route::post('cart/add-draft-item/{draftKey}', 'InvoiceDraftController@addDraftItem')->name('cart.add-draft-item'); |
|||
Route::patch('cart/update-draft-item/{draftKey}', 'InvoiceDraftController@updateDraftItem')->name('cart.update-draft-item'); |
|||
Route::patch('cart/{draftKey}/proccess', 'InvoiceDraftController@proccess')->name('cart.draft-proccess'); |
|||
Route::delete('cart/remove-draft-item/{draftKey}', 'InvoiceDraftController@removeDraftItem')->name('cart.remove-draft-item'); |
|||
Route::delete('cart/empty/{draftKey}', 'InvoiceDraftController@empty')->name('cart.empty'); |
|||
Route::delete('cart/remove', 'InvoiceDraftController@remove')->name('cart.remove'); |
|||
Route::delete('cart/destroy', 'InvoiceDraftController@destroy')->name('cart.destroy'); |
|||
|
|||
/* |
|||
* Invoices Routes |
|||
*/ |
|||
Route::get('invoices/{invoice}', ['as' => 'invoices.show', 'uses' => 'InvoicesController@show']); |
|||
}); |
|||
@ -0,0 +1,169 @@ |
|||
<?php |
|||
|
|||
namespace Tests\Feature; |
|||
|
|||
use App\Entities\Projects\Project; |
|||
use App\Services\InvoiceDrafts\InvoiceDraft; |
|||
use App\Services\InvoiceDrafts\InvoiceDraftCollection; |
|||
use App\Services\InvoiceDrafts\Item; |
|||
use Tests\TestCase; |
|||
|
|||
class InvoiceEntryTest extends TestCase |
|||
{ |
|||
/** @test */ |
|||
public function user_can_visit_invoice_drafts_page() |
|||
{ |
|||
$this->adminUserSigningIn(); |
|||
|
|||
// Add new draft to collection
|
|||
$cart = new InvoiceDraftCollection(); |
|||
$draft = $cart->add(new InvoiceDraft()); |
|||
|
|||
$this->visit(route('invoices.create')); |
|||
|
|||
$this->assertViewHas('draft', $draft); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function user_can_create_invoice_draft_by_invoice_create_button() |
|||
{ |
|||
$this->adminUserSigningIn(); |
|||
$this->visit(route('invoices.create')); |
|||
|
|||
$this->press(trans('invoice.create')); |
|||
$cart = new InvoiceDraftCollection(); |
|||
$draft = $cart->content()->last(); |
|||
$this->seePageIs(route('invoices.create', $draft->draftKey)); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function user_can_add_item_to_cash_draft() |
|||
{ |
|||
$this->adminUserSigningIn(); |
|||
|
|||
$cart = new InvoiceDraftCollection(); |
|||
$draft = new InvoiceDraft(); |
|||
$cart->add($draft); |
|||
|
|||
$this->visit(route('invoices.create', [$draft->draftKey])); |
|||
|
|||
$this->type('Testing deskripsi invoice item', 'description'); |
|||
$this->type(2000, 'amount'); |
|||
$this->press('add-item'); |
|||
|
|||
$this->see(trans('invoice.item_added')); |
|||
|
|||
$this->type('Testing deskripsi invoice item', 'description'); |
|||
$this->type(3000, 'amount'); |
|||
$this->press('add-item'); |
|||
|
|||
$this->seePageIs(route('invoices.create', $draft->draftKey)); |
|||
$this->assertEquals(5000, $draft->getTotal()); |
|||
$this->see(formatRp(5000)); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function user_can_update_item_attribute() |
|||
{ |
|||
$cart = new InvoiceDraftCollection(); |
|||
|
|||
$draft = $cart->add(new InvoiceDraft()); |
|||
|
|||
$item1 = new Item(['description' => 'Deskripsi item invoice', 'amount' => 1000]); |
|||
$item2 = new Item(['description' => 'Deskripsi item invoice', 'amount' => 2000]); |
|||
|
|||
// Add items to draft
|
|||
$cart->addItemToDraft($draft->draftKey, $item1); |
|||
$cart->addItemToDraft($draft->draftKey, $item2); |
|||
|
|||
$this->adminUserSigningIn(); |
|||
$this->visit(route('invoices.create', $draft->draftKey)); |
|||
|
|||
$this->submitForm('update-item-0', [ |
|||
'item_key' => 0, |
|||
'description' => 'Testing deskripsi Update', |
|||
'amount' => 100, |
|||
]); |
|||
|
|||
$this->submitForm('update-item-1', [ |
|||
'item_key' => 1, |
|||
'description' => 'Testing deskripsi Update', |
|||
'amount' => 100, |
|||
]); |
|||
|
|||
$this->assertEquals(200, $draft->getTotal()); |
|||
|
|||
$this->see(formatRp($draft->getTotal())); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function user_can_update_draft_transaction_detail_and_get_confirm_page() |
|||
{ |
|||
$project = factory(Project::class)->create(); |
|||
$cart = new InvoiceDraftCollection(); |
|||
|
|||
$draft = $cart->add(new InvoiceDraft()); |
|||
|
|||
$item1 = new Item(['description' => 'Deskripsi item invoice', 'amount' => 1000]); |
|||
$item2 = new Item(['description' => 'Deskripsi item invoice', 'amount' => 2000]); |
|||
|
|||
// Add items to draft
|
|||
$cart->addItemToDraft($draft->draftKey, $item1); |
|||
$cart->addItemToDraft($draft->draftKey, $item2); |
|||
|
|||
$this->adminUserSigningIn(); |
|||
$this->visit(route('invoices.create', $draft->draftKey)); |
|||
|
|||
$this->type($project->id, 'project_id'); |
|||
$this->type('catatan', 'notes'); |
|||
$this->press(trans('invoice.proccess')); |
|||
|
|||
$this->seePageIs(route('invoices.create', [$draft->draftKey, 'action' => 'confirm'])); |
|||
|
|||
$this->see(trans('invoice.confirm')); |
|||
$this->see($project->name); |
|||
$this->see($draft->notes); |
|||
$this->see(formatRp(3000)); |
|||
$this->seeElement('input', ['id' => 'save-invoice-draft']); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function user_can_save_transaction_if_draft_is_completed() |
|||
{ |
|||
$cart = new InvoiceDraftCollection(); |
|||
|
|||
$draft = $cart->add(new InvoiceDraft()); |
|||
|
|||
$item1 = new Item(['description' => 'Deskripsi item invoice', 'amount' => 1000]); |
|||
$item2 = new Item(['description' => 'Deskripsi item invoice', 'amount' => 2000]); |
|||
$project = factory(Project::class)->create(); |
|||
|
|||
// Add items to draft
|
|||
$cart->addItemToDraft($draft->draftKey, $item1); |
|||
$cart->addItemToDraft($draft->draftKey, $item2); |
|||
|
|||
$draftAttributes = [ |
|||
'project_id' => $project->id, |
|||
'notes' => 'Catatan', |
|||
]; |
|||
$cart->updateDraftAttributes($draft->draftKey, $draftAttributes); |
|||
|
|||
$user = $this->adminUserSigningIn(); |
|||
$this->visit(route('invoices.create', [$draft->draftKey, 'action' => 'confirm'])); |
|||
|
|||
$this->press(trans('invoice.save')); |
|||
|
|||
// $this->seePageIs(route('invoices.show', date('ym').'0001'));
|
|||
// $this->see(trans('invoice.created', ['invoice_no' => date('ym').'0001']));
|
|||
|
|||
$this->seeInDatabase('invoices', [ |
|||
'invoice_no' => date('ym').'0001', |
|||
'items' => '[{"description":"Deskripsi item invoice","amount":1000},{"description":"Deskripsi item invoice","amount":2000}]', |
|||
'project_id' => $project->id, |
|||
'amount' => 3000, |
|||
'notes' => 'Catatan', |
|||
'user_id' => $user->id, |
|||
'status_id' => 1, |
|||
]); |
|||
} |
|||
} |
|||
@ -0,0 +1,152 @@ |
|||
<?php |
|||
|
|||
namespace Tests\Unit; |
|||
|
|||
use App\Services\InvoiceDrafts\Item; |
|||
use App\Services\InvoiceDrafts\InvoiceDraft; |
|||
use App\Services\InvoiceDrafts\InvoiceDraftCollection; |
|||
use Tests\TestCase; |
|||
|
|||
class InvoiceDraftCollectionTest extends TestCase |
|||
{ |
|||
/** @test */ |
|||
public function it_has_a_default_instance() |
|||
{ |
|||
$cart = new InvoiceDraftCollection(); |
|||
$this->assertEquals('drafts', $cart->currentInstance()); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function it_can_have_multiple_instances() |
|||
{ |
|||
$cart = new InvoiceDraftCollection(); |
|||
|
|||
$draft = new InvoiceDraft(); |
|||
|
|||
$cart->add($draft); |
|||
$cart->instance('wishlist')->add($draft); |
|||
|
|||
$this->assertCount(1, $cart->instance('drafts')->content()); |
|||
$this->assertCount(1, $cart->instance('wishlist')->content()); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function cart_collection_consist_of_invoice_draft() |
|||
{ |
|||
$cart = new InvoiceDraftCollection(); |
|||
$draft = new InvoiceDraft(); |
|||
|
|||
$cart->add($draft); |
|||
|
|||
$this->assertCount(1, $cart->content()); |
|||
$this->assertTrue($cart->hasContent()); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function it_can_get_a_draft_by_key() |
|||
{ |
|||
$draft = new InvoiceDraft(); |
|||
$cart = new InvoiceDraftCollection(); |
|||
|
|||
$cart->add($draft); |
|||
$gottenDraft = $cart->get($draft->draftKey); |
|||
$invalidDraft = $cart->get('random_key'); |
|||
|
|||
$this->assertEquals($draft, $gottenDraft); |
|||
$this->assertNull($invalidDraft); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function it_can_remove_draft_from_draft_collection() |
|||
{ |
|||
$cart = new InvoiceDraftCollection(); |
|||
$draft = new InvoiceDraft(); |
|||
|
|||
$cart->add($draft); |
|||
|
|||
$this->assertCount(1, $cart->content()); |
|||
$cart->removeDraft($cart->content()->keys()->last()); |
|||
$this->assertCount(0, $cart->content()); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function it_can_be_empty_out() |
|||
{ |
|||
$cart = new InvoiceDraftCollection(); |
|||
|
|||
$draft = new InvoiceDraft(); |
|||
|
|||
$cart->add($draft); |
|||
$cart->add($draft); |
|||
$cart->add($draft); |
|||
|
|||
$this->assertCount(3, $cart->content()); |
|||
$cart->destroy(); |
|||
|
|||
$this->assertCount(0, $cart->content()); |
|||
$this->assertTrue($cart->isEmpty()); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function it_has_content_keys() |
|||
{ |
|||
$cart = new InvoiceDraftCollection(); |
|||
|
|||
$draft = new InvoiceDraft(); |
|||
|
|||
$cart->add($draft); |
|||
|
|||
$this->assertCount(1, $cart->keys()); |
|||
$cart->removeDraft($cart->content()->keys()->last()); |
|||
$this->assertCount(0, $cart->keys()); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function it_can_add_item_to_draft() |
|||
{ |
|||
$cart = new InvoiceDraftCollection(); |
|||
|
|||
$draft = $cart->add(new InvoiceDraft()); |
|||
$item = new Item(['description' => 'Deskripsi item invoice', 'amount' => 1000]); |
|||
|
|||
$cart->addItemToDraft($draft->draftKey, $item); |
|||
$cart->addItemToDraft($draft->draftKey, $item); |
|||
$this->assertEquals(2000, $draft->getTotal()); |
|||
$this->assertEquals(2, $draft->getItemsCount()); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function it_can_remove_item_from_draft() |
|||
{ |
|||
$cart = new InvoiceDraftCollection(); |
|||
|
|||
$draft = $cart->add(new InvoiceDraft()); |
|||
$item = new Item(['description' => 'Deskripsi item invoice', 'amount' => 1000]); |
|||
|
|||
$cart->addItemToDraft($draft->draftKey, $item); |
|||
$this->assertCount(1, $draft->items()); |
|||
$cart->removeItemFromDraft($draft->draftKey, 0); |
|||
$this->assertCount(0, $draft->items()); |
|||
$this->assertEquals(0, $draft->getTotal()); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function it_can_update_an_item_of_draft() |
|||
{ |
|||
$cart = new InvoiceDraftCollection(); |
|||
|
|||
$draft = $cart->add(new InvoiceDraft()); |
|||
$item = new Item(['description' => 'Deskripsi item invoice', 'amount' => 1000]); |
|||
|
|||
$cart->addItemToDraft($draft->draftKey, $item); |
|||
$this->assertCount(1, $draft->items()); |
|||
$this->assertEquals(1000, $draft->getTotal()); |
|||
|
|||
$newItemData = [ |
|||
'amount' => 100, |
|||
]; |
|||
|
|||
$cart->updateDraftItem($draft->draftKey, 0, $newItemData); |
|||
$this->assertEquals(100, $draft->getTotal()); |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue