Browse Source
Add sales report
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 librarypull/14/head
15 changed files with 564 additions and 3 deletions
-
91app/Helpers/date_time.php
-
93app/Http/Controllers/Reports/SalesController.php
-
2app/Providers/AppServiceProvider.php
-
2public/css/plugins/morris.css
-
7public/js/plugins/morris.min.js
-
11public/js/plugins/raphael.min.js
-
5resources/lang/id/app.php
-
29resources/lang/id/report.php
-
21resources/lang/id/time.php
-
1resources/lang/id/transaction.php
-
3resources/views/layouts/partials/top-nav.blade.php
-
80resources/views/reports/sales/daily.blade.php
-
105resources/views/reports/sales/monthly.blade.php
-
101resources/views/reports/sales/yearly.blade.php
-
14routes/web.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; |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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
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
File diff suppressed because it is too large
View File
@ -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', |
|||
]; |
|||
@ -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', |
|||
], |
|||
]; |
|||
@ -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> </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 |
|||
@ -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> </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 |
|||
@ -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> </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 |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue