Browse Source

Add sales report

Sales report based on:
- Omzet by year (graph and table)
- Omzet by month (graph and table)
- Omzet by date transaction list

Add new date_time helper functions
Add morris js chart library
pull/14/head
Nafies Luthfi 8 years ago
parent
commit
3fee57c0fb
  1. 91
      app/Helpers/date_time.php
  2. 93
      app/Http/Controllers/Reports/SalesController.php
  3. 2
      app/Providers/AppServiceProvider.php
  4. 2
      public/css/plugins/morris.css
  5. 7
      public/js/plugins/morris.min.js
  6. 11
      public/js/plugins/raphael.min.js
  7. 5
      resources/lang/id/app.php
  8. 29
      resources/lang/id/report.php
  9. 21
      resources/lang/id/time.php
  10. 1
      resources/lang/id/transaction.php
  11. 3
      resources/views/layouts/partials/top-nav.blade.php
  12. 80
      resources/views/reports/sales/daily.blade.php
  13. 105
      resources/views/reports/sales/monthly.blade.php
  14. 101
      resources/views/reports/sales/yearly.blade.php
  15. 14
      routes/web.php

91
app/Helpers/date_time.php

@ -0,0 +1,91 @@
<?php
function formatDate($date)
{
if (!$date || $date == '0000-00-00') {
return;
}
$explodedDate = explode('-', $date);
if (count($explodedDate) == 3 && checkdate($explodedDate[1], $explodedDate[0], $explodedDate[2])) {
return $explodedDate[2].'-'.$explodedDate[1].'-'.$explodedDate[0];
} elseif (count($explodedDate) == 3 && checkdate($explodedDate[1], $explodedDate[2], $explodedDate[0])) {
return $explodedDate[2].'-'.$explodedDate[1].'-'.$explodedDate[0];
}
throw new App\Exceptions\InvalidDateException('Invalid date format.');
}
function dateId($date)
{
if (is_null($date) || $date == '0000-00-00') {
return '-';
}
$explodedDate = explode('-', $date);
if (count($explodedDate) == 3 && checkdate($explodedDate[1], $explodedDate[2], $explodedDate[0])) {
$months = getMonths();
return $explodedDate[2].' '.$months[$explodedDate[1]].' '.$explodedDate[0];
}
throw new App\Exceptions\InvalidDateException('Invalid date format.');
}
function monthNumber($number)
{
return str_pad($number, 2, '0', STR_PAD_LEFT);
}
function monthId($monthNumber)
{
if (is_null($monthNumber)) {
return $monthNumber;
}
$months = getMonths();
$monthNumber = monthNumber($monthNumber);
return $months[$monthNumber];
}
function getMonths()
{
return [
'01' => __('time.months.01'),
'02' => __('time.months.02'),
'03' => __('time.months.03'),
'04' => __('time.months.04'),
'05' => __('time.months.05'),
'06' => __('time.months.06'),
'07' => __('time.months.07'),
'08' => __('time.months.08'),
'09' => __('time.months.09'),
'10' => __('time.months.10'),
'11' => __('time.months.11'),
'12' => __('time.months.12'),
];
}
function getYears()
{
$yearRange = range(2017, date('Y'));
foreach ($yearRange as $year) {
$years[$year] = $year;
}
return $years;
}
function monthDateArray($year, $month)
{
$dateCount = Carbon\Carbon::parse($year.'-'.$month)->format('t');
$dates = [];
foreach (range(1, $dateCount) as $dateNumber) {
$dates[] = str_pad($dateNumber, 2, '0', STR_PAD_LEFT);
}
return $dates;
}

93
app/Http/Controllers/Reports/SalesController.php

@ -0,0 +1,93 @@
<?php
namespace App\Http\Controllers\Reports;
use App\Http\Controllers\Controller;
use App\Transaction;
use DB;
use Illuminate\Http\Request;
/**
* Reports Controller.
*
* @author Nafies Luthfi <nafiesL@gmail.com>
*/
class SalesController extends Controller
{
public function daily(Request $request)
{
$date = $request->get('date', date('Y-m-d'));
$transactions = Transaction::orderBy('created_at', 'desc')
->where('created_at', 'like', $date.'%')
->get();
return view('reports.sales.daily', compact('transactions', 'date'));
}
public function monthly(Request $request)
{
$years = getYears();
$months = getMonths();
$year = $request->get('year', date('Y'));
$month = $request->get('month', date('m'));
$reports = $this->getMonthlyReports($year, $month);
return view('reports.sales.monthly', compact('reports', 'months', 'years', 'month', 'year'));
}
public function yearly(Request $request)
{
$year = $request->get('year', date('Y'));
$reports = $this->getYearlyReports($year);
$years = getYears();
return view('reports.sales.yearly', compact('reports', 'years', 'year'));
}
private function getMonthlyReports($year, $month)
{
$rawQuery = 'DATE(created_at) as date, count(`id`) as count';
$rawQuery .= ', sum(total) AS amount';
$reportsData = DB::table('transactions')->select(DB::raw($rawQuery))
->where(DB::raw('YEAR(created_at)'), $year)
->where(DB::raw('MONTH(created_at)'), $month)
->groupBy('date')
->orderBy('date', 'asc')
->get();
$reports = [];
foreach ($reportsData as $report) {
$key = substr($report->date, -2);
$reports[$key] = $report;
$reports[$key]->omzet = $report->amount;
}
return collect($reports);
}
private function getYearlyReports($year)
{
$rawQuery = 'MONTH(created_at) as month';
$rawQuery .= ', count(`id`) as count';
$rawQuery .= ', sum(total) AS amount';
$reportsData = DB::table('transactions')->select(DB::raw($rawQuery))
->where(DB::raw('YEAR(created_at)'), $year)
->groupBy(DB::raw('YEAR(created_at)'))
->groupBy(DB::raw('MONTH(created_at)'))
->orderBy('created_at', 'asc')
->get();
$reports = [];
foreach ($reportsData as $report) {
$key = str_pad($report->month, 2, '0', STR_PAD_LEFT);
$reports[$key] = $report;
$reports[$key]->omzet = $report->amount;
}
return collect($reports);
}
}

2
app/Providers/AppServiceProvider.php

@ -14,6 +14,8 @@ class AppServiceProvider extends ServiceProvider
public function boot()
{
require_once app_path().'/Helpers/helpers.php';
require_once app_path().'/Helpers/date_time.php';
\Validator::extend('not_exists', function ($attribute, $value, $parameters) {
return \DB::table($parameters[0])
->where($parameters[1], $value)

2
public/css/plugins/morris.css

@ -0,0 +1,2 @@
.morris-hover{position:absolute;z-index:1000}.morris-hover.morris-default-style{border-radius:10px;padding:6px;color:#666;background:rgba(255,255,255,0.8);border:solid 2px rgba(230,230,230,0.8);font-family:sans-serif;font-size:12px;text-align:center}.morris-hover.morris-default-style .morris-hover-row-label{font-weight:bold;margin:0.25em 0}
.morris-hover.morris-default-style .morris-hover-point{white-space:nowrap;margin:0.1em 0}

7
public/js/plugins/morris.min.js
File diff suppressed because it is too large
View File

11
public/js/plugins/raphael.min.js
File diff suppressed because it is too large
View File

5
resources/lang/id/app.php

@ -12,13 +12,14 @@ return [
'cancel' => 'Batal',
'reset' => 'Reset',
'show' => 'Lihat Detail',
'show_detail_title' => 'Lihat detail :type :name',
'label' => 'Label',
'edit' => 'Edit',
'print' => 'Print',
'search' => 'Cari',
'filter' => 'Filter',
'close' => 'Tutup',
'action' => 'Action',
'action' => 'Pilihan',
'notes' => 'Catatan',
'delete_confirm_button' => 'Ya, silakan hapus!',
'delete_confirm' => 'Anda yakin akan menghapus?',
@ -31,8 +32,8 @@ return [
'created_at' => 'Dibuat Pada',
'created_by' => 'Oleh',
'total' => 'Total',
'subtotal' => 'Subtotal',
'count' => 'Jumlah',
'welcome' => 'Selamat datang',
'export-pdf' => 'Export PDF',
];

29
resources/lang/id/report.php

@ -0,0 +1,29 @@
<?php
return [
// Labels
'report' => 'Laporan',
'sales' => 'Laponan Penjualan',
'view_report' => 'Lihat Laporan',
'sales_graph' => 'Grafik Penjualan',
'detail' => 'Detail Laporan',
'omzet' => 'Penjualan',
// Daily
'daily' => 'Laponan Harian: :date',
'today' => 'Hari Ini',
'view_daily' => 'Lihat Harian',
'view_daily_label' => 'Lihat Harian per',
// Monthly
'monthly' => 'Laponan Bulan :year_month',
'this_month' => 'Bulan Ini',
'view_monthly' => 'Lihat Bulanan',
'view_monthly_label' => 'Lihat Bulanan per',
// Yearly
'yearly' => 'Laponan Tahun :year',
'this_year' => 'Tahun Ini',
'view_yearly' => 'Lihat Tahunan',
'view_yearly_label' => 'Lihat Tahunan per',
];

21
resources/lang/id/time.php

@ -0,0 +1,21 @@
<?php
return [
'date' => 'Tanggal',
'month' => 'Bulan',
'months' => [
'01' => 'Januari',
'02' => 'Pebruari',
'03' => 'Maret',
'04' => 'April',
'05' => 'Mei',
'06' => 'Juni',
'07' => 'Juli',
'08' => 'Agustus',
'09' => 'September',
'10' => 'Oktober',
'11' => 'Nopember',
'12' => 'Desember',
],
];

1
resources/lang/id/transaction.php

@ -23,6 +23,7 @@ return [
'items_count' => 'Jumlah Item',
'cashier' => 'Kasir',
'search' => 'Cari Invoice',
'not_found' => 'Transaksi tidak ditemukan.',
// Actions
'proccess' => 'Proses Transaksi',

3
resources/views/layouts/partials/top-nav.blade.php

@ -25,6 +25,9 @@
<li {{ (Request::segment(1) == 'transactions') ? 'class=active' : '' }}>
{{ link_to_route('transactions.index', trans('transaction.list')) }}
</li>
<li {{ (Request::segment(1) == 'reports') ? 'class=active' : '' }}>
{{ link_to_route('reports.sales.index', trans('report.sales')) }}
</li>
</ul>
<!-- Right Side Of Navbar -->
<ul class="nav navbar-nav navbar-right">

80
resources/views/reports/sales/daily.blade.php

@ -0,0 +1,80 @@
@extends('layouts.app')
@section('title', __('report.daily', ['date' => dateId($date)]))
@section('content')
@php $dt = Carbon\Carbon::parse($date); @endphp
{{ Form::open(['method' => 'get', 'class' => 'form-inline well well-sm']) }}
{{ Form::label('date', __('report.view_daily_label'), ['class' => 'control-label']) }}
{{ Form::text('date', $date, ['required', 'class' => 'form-control', 'style' => 'width:100px']) }}
{{ Form::submit(__('report.view_report'), ['class' => 'btn btn-info btn-sm']) }}
{{ link_to_route('reports.sales.daily', __('report.today'), [], ['class' => 'btn btn-default btn-sm']) }}
{{ link_to_route(
'reports.sales.monthly',
__('report.view_monthly'),
['month' => monthNumber($dt->month), 'year' => $dt->year],
['class' => 'btn btn-default btn-sm']
) }}
{{ Form::close() }}
<div class="panel panel-default table-responsive">
<table class="table table-condensed table-hover">
<thead>
<th class="text-center">{{ __('app.table_no') }}</th>
<th class="text-center">{{ __('app.date') }}</th>
<th class="text-right">{{ __('report.omzet') }}</th>
<th class="text-center">{{ __('app.action') }}</th>
</thead>
<tbody>
@forelse($transactions as $key => $transaction)
<tr>
<td class="text-center">{{ 1 + $key }}</td>
<td class="text-center">{{ dateId($transaction->created_at->format('Y-m-d')) }}</td>
<td class="text-right">{{ formatRp($transaction->total) }}</td>
<td class="text-center">
{{ link_to_route(
'transactions.show',
__('app.show'),
[$transaction],
[
'title' => __('app.show_detail_title', ['name' => $transaction->number, 'type' => trans('transaction.transaction')]),
'target' => '_blank',
'class' => 'btn btn-info btn-xs'
]
) }}
</td>
</tr>
@empty
<tr><td colspan="4">{{ __('transaction.not_found') }}</td></tr>
@endforelse
</tbody>
<tfoot>
<tr>
<th class="text-right" colspan="2">{{ __('app.total') }}</th>
<th class="text-right">{{ formatRp($transactions->sum('total')) }}</th>
<th>&nbsp;</th>
</tr>
</tfoot>
</table>
</div>
@endsection
@section('ext_css')
{{ Html::style(url('css/plugins/jquery.datetimepicker.css')) }}
@endsection
@section('script')
{{ Html::script(url('js/plugins/jquery.datetimepicker.js')) }}
<script>
(function() {
$('#date').datetimepicker({
timepicker:false,
format:'Y-m-d',
closeOnDateSelect: true,
scrollInput: false
});
})();
</script>
@endsection

105
resources/views/reports/sales/monthly.blade.php

@ -0,0 +1,105 @@
@extends('layouts.app')
@section('title', __('report.monthly', ['year_month' => $months[$month].' '.$year]))
@section('content')
{{ Form::open(['method' => 'get', 'class' => 'form-inline well well-sm']) }}
{{ Form::label('month', __('report.view_monthly_label'), ['class' => 'control-label']) }}
{{ Form::select('month', $months, $month, ['class' => 'form-control']) }}
{{ Form::select('year', $years, $year, ['class' => 'form-control']) }}
{{ Form::submit(__('report.view_report'), ['class' => 'btn btn-info btn-sm']) }}
{{ link_to_route('reports.sales.monthly', __('report.this_month'), [], ['class' => 'btn btn-default btn-sm']) }}
{{ link_to_route('reports.sales.yearly', __('report.view_yearly'), ['year' => $year], ['class' => 'btn btn-default btn-sm']) }}
{{ Form::close() }}
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">{{ __('report.sales_graph') }} {{ $months[$month] }} {{ $year }}</h3></div>
<div class="panel-body">
<strong>Rp.</strong>
<div id="monthly-chart" style="height: 250px;"></div>
<div class="text-center"><strong>{{ __('time.date') }}</strong></div>
</div>
</div>
<div class="panel panel-success table-responsive">
<div class="panel-heading"><h3 class="panel-title">{{ __('report.detail') }}</h3></div>
<div class="panel-body">
<table class="table table-condensed">
<thead>
<th class="text-center">{{ __('time.date') }}</th>
<th class="text-center">{{ __('transaction.transaction') }}</th>
<th class="text-right">{{ __('report.omzet') }}</th>
<th class="text-center">{{ __('app.action') }}</th>
</thead>
<tbody>
@php $chartData = []; @endphp
@foreach(monthDateArray($year, $month) as $dateNumber)
@php
$any = isset($reports[$dateNumber]);
$count = $any ? $reports[$dateNumber]->count : 0;
$subtotal = $any ? $reports[$dateNumber]->amount : 0;
@endphp
@if ($any)
<tr>
<td class="text-center">{{ dateId($date = $year.'-'.$month.'-'.$dateNumber) }}</td>
<td class="text-center">{{ $count }}</td>
<td class="text-right">{{ formatRp($subtotal) }}</td>
<td class="text-center">
{{ link_to_route(
'reports.sales.daily',
__('report.view_daily'),
['date' => $date],
[
'class' => 'btn btn-info btn-xs',
'title' => __('report.daily', ['date' => dateId($date)]),
]
) }}
</td>
</tr>
@endif
@php
$chartData[] = ['date' => $dateNumber, 'value' => ($subtotal) ];
@endphp
@endforeach
</tbody>
<tfoot>
<tr>
<th class="text-right">{{ __('app.total') }}</th>
<th class="text-center">{{ $reports->sum('count') }}</th>
<th class="text-right">{{ formatRp($reports->sum('amount')) }}</th>
<td>&nbsp;</td>
</tr>
</tfoot>
</table>
</div>
</div>
@endsection
@section('ext_css')
{{ Html::style(url('css/plugins/morris.css')) }}
@endsection
@push('ext_js')
{{ Html::script(url('js/plugins/raphael.min.js')) }}
{{ Html::script(url('js/plugins/morris.min.js')) }}
@endpush
@section('script')
<script>
(function() {
new Morris.Line({
element: 'monthly-chart',
data: {!! collect($chartData)->toJson() !!},
xkey: 'date',
ykeys: ['value'],
labels: ["{{ __('report.omzet') }} Rp"],
parseTime:false,
xLabelAngle: 30,
goals: [0],
goalLineColors : ['red'],
lineWidth: 2,
});
})();
</script>
@endsection

101
resources/views/reports/sales/yearly.blade.php

@ -0,0 +1,101 @@
@extends('layouts.app')
@section('title', __('report.yearly', ['year' => $year]))
@section('content')
{{ Form::open(['method' => 'get', 'class' => 'form-inline well well-sm']) }}
{{ Form::label('year', __('report.view_yearly_label'), ['class' => 'control-label']) }}
{{ Form::select('year', $years, $year, ['class' => 'form-control']) }}
{{ Form::submit(__('report.view_report'), ['class' => 'btn btn-info btn-sm']) }}
{{ link_to_route('reports.sales.yearly', __('report.this_year'), [], ['class' => 'btn btn-default btn-sm']) }}
{{ Form::close() }}
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">{{ __('report.sales_graph') }} {{ $year }}</h3></div>
<div class="panel-body">
<strong>Rp.</strong>
<div id="yearly-chart" style="height: 250px;"></div>
<div class="text-center"><strong>{{ __('time.month') }}</strong></div>
</div>
</div>
<div class="panel panel-success table-responsive">
<div class="panel-heading"><h3 class="panel-title">{{ __('report.detail') }}</h3></div>
<div class="panel-body table-responsive">
<table class="table table-condensed">
<thead>
<th class="text-center">{{ __('time.month') }}</th>
<th class="text-center">{{ __('transaction.transaction') }}</th>
<th class="text-right">{{ __('report.omzet') }}</th>
<th class="text-center">{{ __('app.action') }}</th>
</thead>
<tbody>
@php $chartData = []; @endphp
@foreach(getMonths() as $monthNumber => $monthName)
@php
$any = isset($reports[$monthNumber]);
$omzet = $any ? $reports[$monthNumber]->omzet : 0
@endphp
<tr>
<td class="text-center">{{ monthId($monthNumber) }}</td>
<td class="text-center">{{ $any ? $reports[$monthNumber]->count : 0 }}</td>
<td class="text-right">{{ formatRp($omzet) }}</td>
<td class="text-center">
{{ link_to_route(
'reports.sales.monthly',
__('report.view_monthly'),
['month' => $monthNumber, 'year' => $year],
[
'class' => 'btn btn-info btn-xs',
'title' => __('report.monthly', ['year_month' => monthId($monthNumber)]),
'title' => __('report.monthly', ['year_month' => monthId($monthNumber).' '.$year]),
]
) }}
</td>
</tr>
@php
$chartData[] = ['month' => monthId($monthNumber), 'value' => $omzet];
@endphp
@endforeach
</tbody>
<tfoot>
<tr>
<th class="text-center">{{ trans('app.total') }}</th>
<th class="text-center">{{ $reports->sum('count') }}</th>
<th class="text-right">{{ formatRp($reports->sum('omzet')) }}</th>
<td>&nbsp;</td>
</tr>
</tfoot>
</table>
</div>
</div>
@endsection
@section('ext_css')
{{ Html::style(url('css/plugins/morris.css')) }}
@endsection
@push('ext_js')
{{ Html::script(url('js/plugins/raphael.min.js')) }}
{{ Html::script(url('js/plugins/morris.min.js')) }}
@endpush
@section('script')
<script>
(function() {
new Morris.Line({
element: 'yearly-chart',
data: {!! collect($chartData)->toJson() !!},
xkey: 'month',
ykeys: ['value'],
labels: ["{{ __('report.omzet') }} Rp"],
parseTime:false,
goals: [0],
goalLineColors : ['red'],
smooth: true,
lineWidth: 2,
});
})();
</script>
@endsection

14
routes/web.php

@ -66,6 +66,20 @@ Route::group(['middleware' => 'auth'], function () {
Route::get('transactions/{transaction}/receipt', ['as' => 'transactions.receipt', 'uses' => 'TransactionsController@receipt']);
/*
* Reports Routes
*/
Route::group(['prefix' => 'reports'], function () {
/*
* Sales Routes
*/
Route::get('sales', ['as' => 'reports.sales.index', 'uses' => 'Reports\SalesController@monthly']);
Route::get('sales/daily', ['as' => 'reports.sales.daily', 'uses' => 'Reports\SalesController@daily']);
Route::get('sales/monthly', ['as' => 'reports.sales.monthly', 'uses' => 'Reports\SalesController@monthly']);
Route::get('sales/yearly', ['as' => 'reports.sales.yearly', 'uses' => 'Reports\SalesController@yearly']);
});
/*
* Backup Restore Database Routes
*/
Route::post('backups/upload', ['as' => 'backups.upload', 'uses' => 'BackupsController@upload']);

Loading…
Cancel
Save