Pendahuluan
Jika transaksi adalah inti dari sebuah aplikasi point of sales, maka laporan juga tak kalah penting guna menunjang informasi yang akan didapatkan oleh user terkait data transaksi dari aplikasi yang sedang digunakannya. Pada modul ini, kita akan belajar bagaimana membuat laporan dari data transaksi hingga menghasilkan output seperti: pdf, excel, dan juga informasi transaksi berdasarkan range tanggal yang ingin dilihat.
Nah, bagi kamu yang mendapati artikel ini, harap untuk mengikutinya dari Part 1, karena artikel ini dibuat dengan tujuan untuk pembelajaran, sedangkan source code yang terlampir di Github hanya sekedar untuk dijadikan referensi bagi teman-teman yang kesulitan untuk merangkai tiap potongan code yang ada.
Baca Juga: Membuat Aplikasi POS (Point of Sales) Laravel 5.6 - Transaksi (Bagian 2)
Form Manajemen Transaksi
Fungsi dari fitur ini adalah untuk meng-handle data transaksi yang telah dilakukan pada halaman transaksi, sehingga kita dapat melihat secara detail data dari transaksi untuk setiap invoice-nya. Hal lain yang ingin kita capai adalah menampilkan berapa total item yang terjual, total omset, dan total pelanggan yang melakukan transaksi dari data yang sedang ditampilkan.
Secara default, data yang akan di-load adalah sebanyak 10 records terbaru, adapun data lainnya akan ditampilkan berdasarkan filter yang dilakukan. Masing-masing records yang ditampilkan akan memiliki tombol untuk men-generate output berupa PDF ataupun Excel.
Buka file OrderController.php
, kemudian tambahkan method:
public function index(Request $request)
{
//MENGAMBIL DATA CUSTOMER
$customers = Customer::orderBy('name', 'ASC')->get();
//MENGAMBIL DATA USER YANG MEMILIKI ROLE KASIR
$users = User::role('kasir')->orderBy('name', 'ASC')->get();
//MENGAMBIL DATA TRANSAKSI
$orders = Order::orderBy('created_at', 'DESC')->with('order_detail', 'customer');
//JIKA PELANGGAN DIPILIH PADA COMBOBOX
if (!empty($request->customer_id)) {
//MAKA DITAMBAHKAN WHERE CONDITION
$orders = $orders->where('customer_id', $request->customer_id);
}
//JIKA USER / KASIR DIPILIH PADA COMBOBOX
if (!empty($request->user_id)) {
//MAKA DITAMBAHKAN WHERE CONDITION
$orders = $orders->where('user_id', $request->user_id);
}
//JIKA START DATE & END DATE TERISI
if (!empty($request->start_date) && !empty($request->end_date)) {
//MAKA DI VALIDASI DIMANA FORMATNYA HARUS DATE
$this->validate($request, [
'start_date' => 'nullable|date',
'end_date' => 'nullable|date'
]);
//START & END DATE DI RE-FORMAT MENJADI Y-m-d H:i:s
$start_date = Carbon::parse($request->start_date)->format('Y-m-d') . ' 00:00:01';
$end_date = Carbon::parse($request->end_date)->format('Y-m-d') . ' 23:59:59';
//DITAMBAHKAN WHEREBETWEEN CONDITION UNTUK MENGAMBIL DATA DENGAN RANGE
$orders = $orders->whereBetween('created_at', [$start_date, $end_date])->get();
} else {
//JIKA START DATE & END DATE KOSONG, MAKA DI-LOAD 10 DATA TERBARU
$orders = $orders->take(10)->skip(0)->get();
}
//MENAMPILKAN KE VIEW
return view('orders.index', [
'orders' => $orders,
'sold' => $this->countItem($orders),
'total' => $this->countTotal($orders),
'total_customer' => $this->countCustomer($orders),
'customers' => $customers,
'users' => $users
]);
}
Penjelasan: Terdapat 3 buah method yang terlibat, yakni: countItem()
: untuk mengambil total item yang terjual, countTotal()
: untuk mengambil total omset dan countCustomer():
untuk mengambil total customer.
Masih di dalam file OrderController.php
, tambahkan code berikut:
private function countCustomer($orders)
{
//ARRAY KOSONG DIDEFINISIKAN
$customer = [];
//JIKA TERDAPAT DATA YANG AKAN DITAMPILKAN
if ($orders->count() > 0) {
//DI-LOOPING UNTUK MENYIMPAN EMAIL KE DALAM ARRAY
foreach ($orders as $row) {
$customer[] = $row->customer->email;
}
}
//MENGHITUNG TOTAL DATA YANG ADA DI DALAM ARRAY
//DIMANA DATA YANG DUPLICATE AKAN DIHAPUS MENGGUNAKAN ARRAY_UNIQUE
return count(array_unique($customer));
}
private function countTotal($orders)
{
//DEFAULT TOTAL BERNILAI 0
$total = 0;
//JIKA DATA ADA
if ($orders->count() > 0) {
//MENGAMBIL VALUE DARI TOTAL -> PLUCK() AKAN MENGUBAHNYA MENJADI ARRAY
$sub_total = $orders->pluck('total')->all();
//KEMUDIAN DATA YANG ADA DIDALAM ARRAY DIJUMLAHKAN
$total = array_sum($sub_total);
}
return $total;
}
private function countItem($order)
{
//DEFAULT DATA 0
$data = 0;
//JIKA DATA TERSEDIA
if ($order->count() > 0) {
//DI-LOOPING
foreach ($order as $row) {
//UNTUK MENGAMBIL QTY
$qty = $row->order_detail->pluck('qty')->all();
//KEMUDIAN QTY DIJUMLAHKAN
$val = array_sum($qty);
$data += $val;
}
}
return $data;
}
public function invoicePdf($invoice)
{
}
public function invoiceExcel($invoice)
{
}
Note: Dua method kosong diakhir untuk didefinisikan ke route agar tidak terjadi error ketika load halaman nantinya, karena method ini akan kita preteli pada sub topik berikutnya.
Jangan lupa untuk menambahkan use statement:
use Carbon\Carbon;
use App\User;
Buat file index.blade.php
di dalam folder resources/views/orders
, kemudian tambahkan code berikut:
@extends('layouts.master')
@section('title')
<title>Manajemen Order</title>
@endsection
@section('content')
<div class="content-wrapper">
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1 class="m-0 text-dark">List Transaksi</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{ route('home') }}">Home</a></li>
<li class="breadcrumb-item active">Order</li>
</ol>
</div>
</div>
</div>
</div>
<section class="content" id="dw">
<div class="container-fluid">
<div class="row">
<!-- FORM UNTUK FILTER DATA -->
<div class="col-md-12">
@card
@slot('title')
Filter Transaksi
@endslot
<form action="{{ route('order.index') }}" method="get">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="">Mulai Tanggal</label>
<input type="text" name="start_date"
class="form-control {{ $errors->has('start_date') ? 'is-invalid':'' }}"
id="start_date"
value="{{ request()->get('start_date') }}"
>
</div>
<div class="form-group">
<label for="">Sampai Tanggal</label>
<input type="text" name="end_date"
class="form-control {{ $errors->has('end_date') ? 'is-invalid':'' }}"
id="end_date"
value="{{ request()->get('end_date') }}">
</div>
<div class="form-group">
<button class="btn btn-primary btn-sm">Cari</button>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="">Pelanggan</label>
<select name="customer_id" class="form-control">
<option value="">Pilih</option>
@foreach ($customers as $cust)
<option value="{{ $cust->id }}"
{{ request()->get('customer_id') == $cust->id ? 'selected':'' }}>
{{ $cust->name }} - {{ $cust->email }}
</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="">Kasir</label>
<select name="user_id" class="form-control">
<option value="">Pilih</option>
@foreach ($users as $user)
<option value="{{ $user->id }}"
{{ request()->get('user_id') == $user->id ? 'selected':'' }}>
{{ $user->name }} - {{ $user->email }}
</option>
@endforeach
</select>
</div>
</div>
</div>
</form>
@slot('footer')
@endslot
@endcard
</div>
<!-- FORM UNTUK MENAMPILKAN DATA TRANSAKSI -->
<div class="col-md-12">
@card
@slot('title')
Data Transaksi
@endslot
<!-- KOTAK UNTUK MENAMPILKAN TOTAL DATA -->
<div class="row">
<div class="col-4">
<div class="small-box bg-info">
<div class="inner">
<h3>{{ $sold }}</h3>
<p>Item Terjual</p>
</div>
<div class="icon">
<i class="ion ion-bag"></i>
</div>
</div>
</div>
<div class="col-4">
<div class="small-box bg-success">
<div class="inner">
<h3>Rp {{ number_format($total) }}</h3>
<p>Total Omset</p>
</div>
<div class="icon">
<i class="ion ion-stats-bars"></i>
</div>
</div>
</div>
<div class="col-4">
<div class="small-box bg-primary">
<div class="inner">
<h3>{{ $total_customer }}</h3>
<p>Total pelanggan</p>
</div>
<div class="icon">
<i class="ion ion-stats-bars"></i>
</div>
</div>
</div>
</div>
<!-- TABLE UNTUK MENAMPILKAN DATA TRANSAKSI -->
<div class="table-responsive">
<table class="table table-hover table-bordered">
<thead>
<tr>
<th>Invoice</th>
<th>Pelanggan</th>
<th>No Telp</th>
<th>Total Belanja</th>
<th>Kasir</th>
<th>Tgl Transaksi</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
<!-- LOOPING MENGGUNAKAN FORELSE, DIRECTIVE DI LARAVEL 5.6 -->
@forelse ($orders as $row)
<tr>
<td><strong>#{{ $row->invoice }}</strong></td>
<td>{{ $row->customer->name }}</td>
<td>{{ $row->customer->phone }}</td>
<td>Rp {{ number_format($row->total) }}</td>
<td>{{ $row->user->name }}</td>
<td>{{ $row->created_at->format('d-m-Y H:i:s') }}</td>
<td>
<a href="{{ route('order.pdf', $row->invoice) }}"
target="_blank"
class="btn btn-primary btn-sm">
<i class="fa fa-print"></i>
</a>
<a href="{{ route('order.excel', $row->invoice) }}"
target="_blank"
class="btn btn-info btn-sm">
<i class="fa fa-file-excel-o"></i>
</a>
</td>
</tr>
@empty
<tr>
<td class="text-center" colspan="7">Tidak ada data transaksi</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@slot('footer')
@endslot
@endcard
</div>
</div>
</div>
</section>
</div>
@endsection
@section('js')
<script>
$('#start_date').datepicker({
autoclose: true,
format: 'yyyy-mm-dd'
});
$('#end_date').datepicker({
autoclose: true,
format: 'yyyy-mm-dd'
});
</script>
@endsection
Buka file routes/web.php
, kemudian tambahkan route berikut:
Route::group(['middleware' => ['role:admin,kasir']], function() {
Route::get('/order', 'OrderController@index')->name('order.index');
Route::get('/order/pdf/{invoice}', 'OrderController@invoicePdf')->name('order.pdf');
Route::get('/order/excel/{invoice}', 'OrderController@invoiceExcel')->name('order.excel');
});
Note: Diletakkan ke dalam middleware dengan role: admin, kasir. Karena schema yang di-inginkan adalah fitur ini dapat diakses oleh admin maupun kasir.
Buka file resources/views/layouts/module/sidebar.blade.php
, kemudian tambahkan:
<li class="nav-item has-treeview">
<a href="#" class="nav-link">
<i class="nav-icon fa fa-shopping-bag"></i>
<p>
Manajemen Order
<i class="right fa fa-angle-left"></i>
</p>
</a>
<ul class="nav nav-treeview">
<li class="nav-item">
<a href="{{ route('order.index') }}" class="nav-link">
<i class="fa fa-circle-o nav-icon"></i>
<p>Order</p>
</a>
</li>
</ul>
</li>
Note: Saya meletakkannya tepat berada dibawah code untuk menu transaksi (baca: diluar directive check role: kasir, karena tidak hanya ingin diakses oleh kasir, melainkan juga admin).
Karena pada method index di controller, kita menggunakan eager loading dari Eloquent, maka jangan lupa untuk mendefinisikannya pada masing-masing model yang terkait. Buka file Order.php
, kemudian tambahkan:
protected $dates = ['created_at'];
public function customer()
{
return $this->belongsTo(Customer::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
Buka file Customer.php
kemudian tambahkan:
public function getNameAttribute($value)
{
return ucfirst($value);
}
public function order()
{
return $this->hasMany(Order::class);
}
Terakhir, buka file User.php
, kemudian tambahkan:
public function getNameAttribute($value)
{
return ucfirst($value);
}
public function order()
{
return $this->hasMany(Order::class);
}
Baca Juga: Belajar Flutter Basic #1: Mengenal & Install Flutter
Generate PDF
Module ini akan men-generate pdf untuk masing-masing invoice yang kemudian nantinya dapat dicetak oleh user. Untuk membuat module tersebut, kita akan memanfaatkan salah satu package yang sudah sangat familiar untuk pengguna Laravel, yakni Laravel Dompdf.
Install package tersebut dengan menggunakan command:
composer require barryvdh/laravel-dompdf
Buka file OrderController.php
, kemudian modifikasi method invoicePdf
public function invoicePdf($invoice)
{
//MENGAMBIL DATA TRANSAKSI BERDASARKAN INVOICE
$order = Order::where('invoice', $invoice)
->with('customer', 'order_detail', 'order_detail.product')->first();
//SET CONFIG PDF MENGGUNAKAN FONT SANS-SERIF
//DENGAN ME-LOAD VIEW INVOICE.BLADE.PHP
$pdf = PDF::setOptions(['dpi' => 150, 'defaultFont' => 'sans-serif'])
->loadView('orders.report.invoice', compact('order'));
return $pdf->stream();
}
Jangan lupa untuk menambahkan use statement:
use PDF;
Karena kita menggunakan fitur eager loading dari Eloquent, jangan lupa definisikan fungsi yang digunakan. Buka file Order_detail.php
, kemudian tambahkan:
public function product()
{
return $this->belongsTo(Product::class);
}
Buat file invoice.blade.php
yang diletakkan di dalam folder orders/report
, kemudian tambahkan code berikut:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Invoice #{{ $order->invoice }}</title>
<style>
body{
padding: 0;
margin: 0;
}
.page{
max-width: 80em;
margin: 0 auto;
}
table th,
table td{
text-align: left;
}
table.layout{
width: 100%;
border-collapse: collapse;
}
table.display{
margin: 1em 0;
}
table.display th,
table.display td{
border: 1px solid #B3BFAA;
padding: .5em 1em;
}
table.display th{ background: #D5E0CC; }
table.display td{ background: #fff; }
table.responsive-table{
box-shadow: 0 1px 10px rgba(0, 0, 0, 0.2);
}
.listcust {
margin: 0;
padding: 0;
list-style: none;
display:table;
border-spacing: 10px;
border-collapse: separate;
list-style-type: none;
}
.customer {
padding-left: 600px;
}
</style>
</head>
<body>
<div class="header">
<h3>Point of Sales Daengweb.id</h3>
<h4 style="line-height: 0px;">Invoice: #{{ $order->invoice }}</h4>
<p><small style="opacity: 0.5;">{{ $order->created_at->format('d-m-Y H:i:s') }}</small></p>
</div>
<div class="customer">
<table>
<tr>
<th>Nama Pelanggan</th>
<td>:</td>
<td>{{ $order->customer->name }}</td>
</tr>
<tr>
<th>No Telp</th>
<td>:</td>
<td>{{ $order->customer->phone }}</td>
</tr>
<tr>
<th>Alamat</th>
<td>:</td>
<td>{{ $order->customer->address }}</td>
</tr>
</table>
</div>
<div class="page">
<table class="layout display responsive-table">
<thead>
<tr>
<th>#</th>
<th>Produk</th>
<th>Harga</th>
<th>Jumlah</th>
<th>Subtotal</th>
</tr>
</thead>
<tbody>
@php
$no = 1;
$totalPrice = 0;
$totalQty = 0;
$total = 0;
@endphp
@forelse ($order->order_detail as $row)
<tr>
<td>{{ $no++ }}</td>
<td>{{ $row->product->name }}</td>
<td>Rp {{ number_format($row->price) }}</td>
<td>{{ $row->qty }} Item</td>
<td>Rp {{ number_format($row->price * $row->qty) }}</td>
</tr>
@php
$totalPrice += $row->price;
$totalQty += $row->qty;
$total += ($row->price * $row->qty);
@endphp
@empty
<tr>
<td colspan="5" class="text-center">Tidak ada data</td>
</tr>
@endforelse
</tbody>
<tfoot>
<tr>
<th colspan="2">Total</th>
<td>Rp {{ number_format($totalPrice) }}</td>
<td>{{ number_format($totalQty) }} Item</td>
<td>Rp {{ number_format($total) }}</td>
</tr>
</tfoot>
</table>
</div>
</body>
</html>
Note: Hanya sebuah view biasa yang terdiri dari table, heading dan format teks lainnya untuk menampilkan informasi dari invoice yang terkait.
Hasil yang akan diperoleh apabila kita membuka salah satu dari data transaksi yang ada, maka akan men-generate pdf dengan tampilan seperti berikut.
Generate Excel
Sebagai opsi lainnya, kita juga akan menyediakan output berupa excel yang dapat di-download oleh user untuk selanjutnya digunakan sesuai kebutuhan. Salah satu package yang akan kita gunakan adalah Laravel Excel yang dikembangkan oleh Maatwebsite, sedangkan artikel yang telah membahas package ini adalah Membuat Laporan Excel 3.0.
Package ini dapat kamu install menggunakan command:
composer require maatwebsite/excel
Buka file OrderController.php
, kemudian modifikasi method invoiceExcel
:
public function invoiceExcel($invoice)
{
return (new OrderInvoice($invoice))->download('invoice-' . $invoice . '.xlsx');
}
Jangan lupa tambahkan use statement:
use App\Exports\OrderInvoice;
Buat file OrderInvoice.php
didalam folder app/Exports
, kemudian tambahkan code berikut:
<?php
namespace App\Exports;
use Illuminate\Contracts\View\View;
use Maatwebsite\Excel\Concerns\FromView;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Events\AfterSheet;
use Maatwebsite\Excel\Concerns\Exportable;
use App\Order;
class OrderInvoice implements FromView, WithEvents, ShouldAutoSize
{
use Exportable;
public function __construct($invoice)
{
$this->invoice = $invoice;
}
public function registerEvents(): array
{
//MEMANIPULASI CELL
return [
AfterSheet::class => function(AfterSheet $event) {
//CELL TERKAIT AKAN DI-MERGE
$event->sheet->mergeCells('A1:C1');
$event->sheet->mergeCells('A2:B2');
$event->sheet->mergeCells('A3:B3');
//DEFINISIKAN STYLE UNTUK CELL
$styleArray = [
'font' => [
'bold' => true,
],
'alignment' => [
'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER,
],
'borders' => [
'top' => [
'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN,
],
],
'fill' => [
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_GRADIENT_LINEAR,
'rotation' => 90,
'startColor' => [
'argb' => 'FFA0A0A0',
],
'endColor' => [
'argb' => 'FFFFFFFF',
],
],
];
//CELL TERAKAIT AKAN MENGGUNAKAN STYLE DARI $styleArray
$event->sheet->getStyle('A9:E9')->applyFromArray($styleArray);
//FORMATTING STYLE UNTUK CELL TERKAIT
$headCustomer = [
'font' => [
'bold' => true,
]
];
$event->sheet->getStyle('A5:A7')->applyFromArray($headCustomer);
},
];
}
public function view(): View
{
//MENGAMBIL DATA TRANSAKSI BERDASARKAN INVOICE YANG DITERIMA DARI CONTROLLER
$order = Order::where('invoice', $this->invoice)
->with('customer', 'order_detail', 'order_detail.product')->first();
//DATA TERSEBUT DIPASSING KE FILE INVOICE_EXCEL
return view('orders.report.invoice_excel', [
'order' => $order
]);
}
}
Buat file invoice_excel.blade.php
, kemudian tambahkan code berikut:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Invoice #{{ $order->invoice }}</title>
</head>
<body>
<div class="header">
<h3>Point of Sales Daengweb.id</h3>
<h4 style="line-height: 0px;">Invoice: #{{ $order->invoice }}</h4>
<p><small style="opacity: 0.5;">{{ $order->created_at->format('d-m-Y H:i:s') }}</small></p>
<p></p>
</div>
<div class="customer">
<table>
<tr>
<th>Nama Pelanggan</th>
<td>{{ $order->customer->name }}</td>
</tr>
<tr>
<th>No Telp</th>
<td>{{ $order->customer->phone }}</td>
</tr>
<tr>
<th>Alamat</th>
<td>{{ $order->customer->address }}</td>
</tr>
</table>
</div>
<div class="page">
<table class="layout display responsive-table">
<thead>
<tr>
<th>#</th>
<th>Produk</th>
<th>Harga</th>
<th>Jumlah</th>
<th>Subtotal</th>
</tr>
</thead>
<tbody>
@php
$no = 1;
$totalPrice = 0;
$totalQty = 0;
$total = 0;
@endphp
@forelse ($order->order_detail as $row)
<tr>
<td>{{ $no++ }}</td>
<td>{{ $row->product->name }}</td>
<td>Rp {{ number_format($row->price) }}</td>
<td>{{ $row->qty }} Item</td>
<td>Rp {{ number_format($row->price * $row->qty) }}</td>
</tr>
@php
$totalPrice += $row->price;
$totalQty += $row->qty;
$total += ($row->price * $row->qty);
@endphp
@empty
<tr>
<td colspan="5" class="text-center">Tidak ada data</td>
</tr>
@endforelse
</tbody>
<tfoot>
<tr>
<th colspan="2" style="align:center"><strong>Total</strong></th>
<td>Rp {{ number_format($totalPrice) }}</td>
<td>{{ number_format($totalQty) }} Item</td>
<td>Rp {{ number_format($total) }}</td>
</tr>
</tfoot>
</table>
</div>
</body>
</html>
Note: Hanya berupa format HTML untuk menampilkan data transaksi (serupa dengan sub topik pdf).
Maka hasil yang akan diperoleh dari laporan dengan format Excel akan tampak seperti berikut.
Kesimpulan
Sepanjang artikel ini kita telah belajar bagaimana men-filter data berdasarkan range yang dikehendaki, kemudian data yang dihasilkan dapat dikeluarkan dengan format PDF maupun Excel guna menunjang informasi yang didapatkan oleh User.
Maka membuat aplikasi adalah tentang merangkai, merangkai potongan code sehingga menjadi sebuah aplikasi yang diinginkan. Ohya, untuk dokumentasi code dari artikel ini dapat kamu lihat di Github.
Comments