We continue from Part 1 where we created controllers and views for Customer and Item models; now, we’ll move onto Invoice model and get the real stuff done. If you happened to land here randomly, then first go through the following articles chronologically since these are the prerequisite before you proceed here any further:
- Invoicing app in Laravel 9 – migrations and models
- Invoicing app in Laravel 9 – invoice template using dompdf
- Invoicing app in Laravel 9 using Bootstrap and jQuery – Part 1
Having followed the steps given in the above articles, we’re ready to build the actual invoicing functionality; so, let’s execute following command in parent (app) folder to create InvoiceController.php;
php artisan make:controller InvoiceController --model=Invoice --resource
Then open app/Http/Controllers/InvoiceController.php and amend it as follows;
<?php
namespace App\Http\Controllers;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\Item;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class InvoiceController extends Controller
{
public function index()
{
$invoices = Invoice::all();
return view('invoices.index', compact('invoices'));
}
public function create()
{
return view('invoices.create');
}
public function store(Request $request)
{
$validated = $request->validate([
'customer_id' => 'required',
'invoice_date' => 'required|date',
'description' => 'nullable|string',
'item_id.*' => 'required',
'quantity.*' => 'required|numeric',
]);
DB::transaction(function () use ($request) {
$invoice = Invoice::create([
'customer_id' => $request->input('customer_id'),
'invoice_number' => $this->createRef(),
'invoice_date' => $request->date('invoice_date'),
'amount' => 0,
'description' => $request->input('description'),
]);
$item_id = $request->input('item_id');
$quantity = $request->input('quantity');
$rate = $request->get('rate');
$num = count($quantity);
for($i=0;$i<$num;$i++){
InvoiceItem::create([
'invoice_id' => $invoice->id,
'item_id' => $item_id[$i],
'quantity' => $quantity[$i],
'rate' => $rate[$i],
'amount' => $quantity[$i] * $rate[$i],
]);
}
$invoice->amount = $invoice->invoiceItems->sum('amount');
$invoice->save();
});
return redirect('invoices')->with('success', 'Invoice created.');
}
public function show(Item $item)
{
}
public function edit(Item $item)
{
}
public function update(Request $request, Item $item)
{
}
public function destroy(Invoice $invoice)
{
DB::transaction(function () use ($invoice) {
foreach ($invoice->invoiceItems as $item) {
$item->delete();
}
$invoice->delete();
});
return back()->with('success', 'Invoice deleted.');
}
public function rate(Request $request)
{
$rate = Item::find($request->get('id'));
return response()->json($rate->sale_rate);;
}
private function createRef()
{
$offset = 5 * 60 * 60;
$timeNdate = gmdate("d-m-Y:H:i", (time() + $offset));
$inv = "";
$last = Invoice::latest()->first();
$expNum = [];
if ($last)
{
$expNum = explode('/', $last->invoice_number);
}
$dateInfo = date_parse_from_format('d-m-Y:H:i', $timeNdate);
if (!$last) {
$inv = 'CO/INV/' . $dateInfo['year'] . '/' . $dateInfo['month'] . '/1';
} else {
$inv = 'CO/INV/' . $dateInfo['year'] . '/' . $dateInfo['month'] . '/' . ($expNum[4] + 1);
}
return $inv;
}
}
Let’s stop and analyze the above code a bit more. In the store() function, we’ve utilized DB::transaction() which makes sure that all the code within the DB::transaction() function gets executed in its entirety. This functionality is crucial when developing a financial app because on a number of occasions we have to make sure that all the entries related to a transaction are completely inserted / updated / deleted before moving ahead; if any of those entries are not entered due to some error, then we have to roll back the transaction in its entirety. Therefore, by employing DB::transaction() in the above code we have made sure that all the invoice items are properly inserted into the database along with invoice entry.
We’ve also created a separate private function createRef() for the purpose of generating invoice reference for each new invoice based on current year, month and the last invoice’s reference number. There is another extra function i.e., rate() which we’ll discuss later on.
Next, in order to define routes, open routes/web.php and put the following line at the upper side of it:
//...
use App\Http\Controllers\InvoiceController;
//...
And then insert following routes down somewhere in this routes/web.php:
//...
Route::resource('invoices', InvoiceController::class)->middleware(['auth']);
Route::get('/rate', [InvoiceController::class, 'rate'])->middleware(['auth']);
We’re done with the controller and routing. Now let’s create Views using Blade templates.
First, create invoices folder inside resources/views. Being inside app folder, you may execute following command in order to make this folder:
mkdir resources/views/invoices
Now create following two files inside resources/views/invoices folder:
- index.blade.php
- create.blade.php
You can execute following command inside parent folder to create these files in one go:
touch resources/views/invoices/{index,create}.blade.php
Next, go inside resources/views/invoices folder and insert following code into resources/views/invoices/index.blade.php:
@extends('layouts.app')
@section('content')
<div class="row">
<div class="m-2">
<div class="p-2">
<h2>Invoices</h2>
</div>
<div class="p-2">
<a class="btn btn-primary" href="{{ route('invoices.create') }}">Create New Invoice</a>
</div>
</div>
</div>
@if ($message = session('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ $message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
<table class="table table-hover">
<tr>
<th>Date</th>
<th>Invoice Ref.</th>
<th>Customer</th>
<th>Description</th>
<th>Amount</th>
<th class="d-flex justify-content-center">Actions</th>
</tr>
@foreach ($invoices as $invoice)
<tr>
<td>{{ \Carbon\Carbon::parse($invoice->invoice_date)->format('F d, Y')}}</td>
<td>{{ $invoice->invoice_number }}</td>
<td>{{ $invoice->customer->name }}</td>
<td>{{ $invoice->description }}</td>
<td>USD {{ $invoice->amount }}</td>
<td class="d-flex justify-content-around">
<a class="btn btn-sm btn-secondary" href="{{ route('pdf', $invoice->id) }}">Generate PDF</a>
<form action="{{ route('invoices.destroy', $invoice->id) }}" method="POST">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
</form>
</td>
</tr>
@endforeach
</table>
@endsection
And then insert following code into resources/views/invoices/create.blade.php;
@extends('layouts.app')
@section('content')
<div class="card m-4">
<div class="card-header">
<h4>Create Invoice</h4>
</div>
<div class="card-body">
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="post" action="{{ route('invoices.store') }}" class="prevent-multi">
@csrf
<div class="row p-2">
<div class="col-md-6">
<div class="form-group">
<label for="customer_id">Customer</label>
<select class="form-select form-select-sm" name="customer_id">
<option value="">Select customer:</option>
@foreach (App\Models\Customer::all() as $customer)
<option value="{{$customer->id}}">{{$customer->name}}</option>
@endforeach
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="invoice_date">Date</label>
<input type="text" class="form-control form-control-sm" name="invoice_date" placeholder="YYYY-MM-DD"/>
</div>
</div>
</div>
<div class="row p-2">
<div class="col-md-12">
<div class="form-group">
<label for="description">Description</label>
<input type="text" class="form-control form-control-sm" name="description"/>
</div>
</div>
</div>
<div class="dynamic_rows">
<div class="row p-2">
<div class="col-md-3">
<label for="item_id[]" class="form-label">Item</label>
<select class="form-select form-select-sm change" name="item_id[]">
<option value="">Select item:</option>
@foreach (App\Models\Item::all() as $item)
<option value="{{$item->id}}">{{$item->name}}</option>
@endforeach
</select>
</div>
<div class="col-md-2">
<label for="quantity[]" class="form-label">Quantity</label>
<input type="text" class="form-control form-control-sm quantity qblur" name="quantity[]"/>
</div>
<div class="col-md-2">
<label for="rate[]" class="form-label">Rate</label>
<input type="text" class="form-control form-control-sm rate" name="rate[]" readonly/>
</div>
<div class="col-md-3">
<label for="amount[]" class="form-label">Amount</label>
<input type="text" class="form-control form-control-sm amount" name="amount[]" readonly/>
</div>
</div>
</div>
<div class="row p-2">
<div class="col-md-3">
<button class="btn btn-sm btn-secondary add_row">Add Row</button>
<button type="submit" class="btn btn-sm btn-primary m-2 prevent-multi-submit">Enter Invoice</button>
</div>
</div>
</form>
<script>
$(document).ready(function() {
html = '<div class="row p-2">';
html+= '<div class="col-md-3"><select class="form-select form-select-sm change" name="item_id[]">';
html+= '<option value="">Select item:</option>';
html+= '@foreach (App\Models\Item::all() as $item)';
html+= '<option value="{{$item->id}}">{{$item->name}}</option>';
html+= '@endforeach<select></div>';
html+= '<div class="col-md-2"><input type="text" class="form-control form-control-sm quantity qblur" name="quantity[]"/></div>';
html+= '<div class="col-md-2"><input type="text" class="form-control form-control-sm rate" name="rate[]" readonly/></div>';
html+= '<div class="col-md-3"><input type="text" class="form-control form-control-sm amount" name="amount[]" readonly/></div>';
html+= '<div class="col-md-2"><a href="#" class="btn btn-sm btn-danger remove_row">X</a></div></div>';
$('.add_row').click(function(e){
e.preventDefault();
$('.dynamic_rows').append(html);
})
$('.dynamic_rows').on("click",".remove_row", function(e){
e.preventDefault();
$(this).parent('div').parent('div').remove();
})
$('.dynamic_rows').on('change','.change',function(){
var cached = $(this).parent('div').parent('div');
$.get("/rate", {id:$(this).val()}, function(r){
cached.find('input.rate').val(r);
total(cached);
})
})
$('.dynamic_rows').on('blur','.qblur',function(){
var cached = $(this).parent('div').parent('div');
total(cached);
})
function total(c){
var quantity = c.find('input.quantity').val();
var rate = c.find('input.rate').val();
var amount;
if(quantity && rate){
amount = quantity * rate
}
c.find('input.amount').val(amount);
}
$(document).on("keydown", ":input:not(textarea):not(:submit)", function(event) {
if(event.keyCode == 13) {
event.preventDefault();
return false;
}
})
$('.prevent-multi').on('submit', function(){
$('.prevent-multi-submit').attr('disabled','true');
return true;
})
});
</script>
</div>
</div>
@endsection
Let’s analyze this code a bit. Here, we’ve defined a couple of important CSS classes i.e., add_row and dynamic_rows in order to insert new item row and to manipulate invoice’s item rows respectively. On clicking button having add_row class, the contents of html variable (an item row) are appended to the div having dynamic_rows class. Next, we’ve defined a couple of behaviors for dynamic_rows i.e., on change and on blur. First behavior (on change) is associated with change CSS class that is implemented by item select drop-down – whenever there’s any change in item select drop-down, an Ajax call is made to InvoiceController‘s rate() function which returns the selling rate of the respective item and the rate field of the item row gets updated dynamically. Second behavior (on blur) is associated with qblur CSS class that is implemented by quantity input field – whenever there’s any change in quantity input field, total() function is triggered and the amount field of the respective item row gets updated.
As you can see, manipulating HTML elements dynamically with traditional tools like jQuery is quite verbose and that’s why modern JS frameworks exist which make these tasks less bulging.
Here, we wrap up our invoicing app and as a final step we provide a navigation menu to our main routes that we’ve created so far; so, let’s insert menu items within navbar inside resources/views/layouts/app.blade.php right after the comments <!– Left Side Of Navbar –> as given below:
<!-- Left Side Of Navbar -->
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="{{ url('/customers') }}">{{ __('Customers') }}</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="{{ url('/items') }}">{{ __('Items') }}</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="{{ url('/invoices') }}">{{ __('Invoices') }}</a>
</li>
</ul>
Run PHP dev server;
php artisan serv
Open browser and enter following URL (After creating a user login if not already created);
http://localhost:8000/invoices
Create some invoices, generate PDFs and chill – now you have a decent boilerplate to create most of the business solutions using Laravel 9 along with Bootstrap and jQuery. But remember, there are a lot of things missing here such as datatable with capabilities to search, filter, and paginate etc.; the purpose here is to demonstrate the core of a business app as we’d normally develop using this stack (Laravel 9 with Bootstrap and jQuery).
Next, we’ll employ the VILT (Vue Inertia Laravel Tailwind) stack in order to achieve the similar results. So, stay tuned 😉