658 lines
18 KiB
PHP
Raw Permalink Normal View History

2019-11-16 10:21:14 +03:00
<?php
2019-11-23 21:47:20 +03:00
namespace App\Abstracts;
2019-11-16 10:21:14 +03:00
2022-06-16 17:57:43 +03:00
use Akaunting\Apexcharts\Chart;
2020-07-02 01:55:45 +03:00
use App\Events\Report\DataLoaded;
use App\Events\Report\DataLoading;
2020-01-31 12:59:12 +03:00
use App\Events\Report\FilterApplying;
use App\Events\Report\FilterShowing;
use App\Events\Report\GroupApplying;
use App\Events\Report\GroupShowing;
use App\Events\Report\RowsShowing;
2019-11-16 10:21:14 +03:00
use App\Exports\Common\Reports as Export;
use App\Models\Common\Report as Model;
2020-12-24 01:28:38 +03:00
use App\Models\Document\Document;
2022-06-01 10:15:55 +03:00
use App\Models\Setting\Category;
use App\Traits\Charts;
2019-11-16 10:21:14 +03:00
use App\Traits\DateTime;
2021-01-23 12:16:58 +03:00
use App\Traits\SearchString;
2022-06-01 10:15:55 +03:00
use App\Traits\Translations;
use App\Utilities\Date;
use App\Utilities\Export as ExportHelper;
2019-11-16 10:21:14 +03:00
use Illuminate\Support\Str;
abstract class Report
{
2022-07-21 16:36:34 +03:00
use Charts, DateTime, SearchString, Translations;
2019-11-16 10:21:14 +03:00
2020-01-16 15:39:37 +03:00
public $model;
public $default_name = '';
public $category = 'reports.income_expense';
2022-06-01 10:15:55 +03:00
public $icon = 'donut_small';
public $type = 'detail';
2019-11-16 10:21:14 +03:00
2021-07-16 02:35:42 +03:00
public $has_money = true;
2019-11-16 10:21:14 +03:00
public $year;
public $views = [];
public $tables = [];
public $dates = [];
2020-01-27 22:41:08 +03:00
public $row_names = [];
2019-11-16 10:21:14 +03:00
2020-01-27 22:41:08 +03:00
public $row_values = [];
public $footer_totals = [];
2019-11-16 10:21:14 +03:00
public $filters = [];
2020-01-27 22:41:08 +03:00
public $loaded = false;
2022-10-24 14:53:05 +03:00
public $bar_formatter_type = 'money';
public $donut_formatter_type = 'percent';
2019-11-16 10:21:14 +03:00
public $chart = [
2022-06-01 10:15:55 +03:00
'bar' => [
'colors' => [
'#6da252',
2019-11-16 10:21:14 +03:00
],
'yaxis' => [
'labels' => [
'formatter' => '',
],
],
2019-11-16 10:21:14 +03:00
],
2022-06-01 10:15:55 +03:00
'donut' => [
'yaxis' => [
'labels' => [
'formatter' => '',
],
],
2022-06-01 10:15:55 +03:00
],
2019-11-16 10:21:14 +03:00
];
2020-03-10 14:54:20 +03:00
public $column_name_width = 'report-column-name';
public $column_value_width = 'report-column-value';
2020-03-09 17:17:42 +03:00
2022-06-01 10:15:55 +03:00
public $row_tree_nodes = [];
2020-01-27 22:41:08 +03:00
public function __construct(Model $model = null, $load_data = true)
2019-11-16 10:21:14 +03:00
{
$this->setGroups();
2020-01-16 15:39:37 +03:00
if (!$model) {
2019-11-16 10:21:14 +03:00
return;
}
2020-01-16 15:39:37 +03:00
$this->model = $model;
2019-11-16 10:21:14 +03:00
2020-01-27 22:41:08 +03:00
if (!$load_data) {
return;
}
$this->load();
}
2020-01-29 01:06:12 +03:00
abstract public function setData();
2020-01-27 22:41:08 +03:00
public function load()
{
2019-11-16 10:21:14 +03:00
$this->setYear();
$this->setViews();
$this->setTables();
$this->setDates();
$this->setFilters();
$this->setRows();
2020-07-02 01:55:45 +03:00
$this->loadData();
2020-03-10 14:54:20 +03:00
$this->setColumnWidth();
$this->setChartLabelFormatter();
2019-11-16 10:21:14 +03:00
2020-01-27 22:41:08 +03:00
$this->loaded = true;
2019-11-16 10:21:14 +03:00
}
2020-07-02 01:55:45 +03:00
public function loadData()
{
event(new DataLoading($this));
$this->setData();
event(new DataLoaded($this));
}
2020-01-04 13:42:58 +03:00
public function getDefaultName()
2019-11-16 10:21:14 +03:00
{
2020-01-16 15:39:37 +03:00
if (!empty($this->default_name)) {
return trans($this->default_name);
}
2019-11-16 10:21:14 +03:00
return Str::title(str_replace('_', ' ', Str::snake((new \ReflectionClass($this))->getShortName())));
}
public function getCategory()
{
2020-01-16 15:39:37 +03:00
return trans($this->category);
2019-11-16 10:21:14 +03:00
}
2022-06-01 10:15:55 +03:00
public function getCategoryDescription()
{
2022-07-31 12:38:45 +03:00
if (! empty($this->category_description)) {
2022-06-01 10:15:55 +03:00
return trans($this->category_description);
}
return $this->findTranslation([
$this->category . '_desc',
$this->category . '_description',
2022-07-31 12:38:45 +03:00
str_replace('general.', 'reports.', $this->category) . '_desc',
str_replace('general.', 'reports.', $this->category) . '_description',
2022-06-01 10:15:55 +03:00
]);
}
2019-11-16 10:21:14 +03:00
public function getIcon()
{
return $this->icon;
}
2022-06-01 10:15:55 +03:00
public function getCharts($table_key)
{
return [
'bar' => $this->getBarChart($table_key),
'donut' => $this->getDonutChart($table_key),
];
}
public function getBarChart($table_key)
2019-11-16 10:21:14 +03:00
{
2022-06-16 17:57:43 +03:00
$chart = new Chart();
2019-11-16 10:21:14 +03:00
2022-06-01 10:15:55 +03:00
if (empty($this->chart)) {
2020-01-28 00:51:29 +03:00
return $chart;
}
2022-06-01 10:15:55 +03:00
$options = !empty($this->chart[$table_key]) ? $this->chart[$table_key]['bar'] : $this->chart['bar'];
2019-11-16 10:21:14 +03:00
2022-06-01 10:15:55 +03:00
$chart->setType('bar')
->setOptions($options)
2022-08-13 12:37:30 +03:00
->setDefaultLocale($this->getDefaultLocaleOfChart())
->setLocales($this->getLocaleTranslationOfChart())
2022-06-01 10:15:55 +03:00
->setLabels(array_values($this->dates))
->setDataset($this->tables[$table_key], 'column', array_values($this->footer_totals[$table_key]));
2019-11-16 10:21:14 +03:00
2022-06-01 10:15:55 +03:00
return $chart;
}
2019-11-16 10:21:14 +03:00
2022-06-01 10:15:55 +03:00
public function getDonutChart($table_key)
{
2022-06-16 17:57:43 +03:00
$chart = new Chart();
2019-11-16 10:21:14 +03:00
2022-06-01 10:15:55 +03:00
if (empty($this->chart)) {
return $chart;
}
$tmp_values = [];
if (! empty($this->row_values[$table_key])) {
foreach ($this->row_values[$table_key] as $id => $dates) {
$tmp_values[$id] = 0;
foreach ($dates as $date) {
$tmp_values[$id] += $date;
}
2019-11-16 10:21:14 +03:00
}
}
2022-06-01 10:15:55 +03:00
$tmp_values = collect($tmp_values)->sort()->reverse()->take(10)->all();
$total = array_sum($tmp_values);
$total = !empty($total) ? $total : 1;
$group = $this->getSetting('group');
$labels = $colors = $values = [];
foreach ($tmp_values as $id => $value) {
$labels[$id] = $this->row_names[$table_key][$id];
$colors[$id] = ($group == 'category')
? Category::withSubCategory()->find($id)?->colorHexCode
: $this->randHexColor();
2022-06-01 10:15:55 +03:00
$values[$id] = round(($value * 100 / $total), 0);
}
$options = !empty($this->chart[$table_key]) ? $this->chart[$table_key]['donut'] : $this->chart['donut'];
$chart->setType('donut')
->setOptions($options)
2022-08-13 12:37:30 +03:00
->setDefaultLocale($this->getDefaultLocaleOfChart())
->setLocales($this->getLocaleTranslationOfChart())
2022-06-01 10:15:55 +03:00
->setLabels(array_values($labels))
->setColors(array_values($colors))
->setDataset($this->tables[$table_key], 'donut', array_values($values));
2022-06-10 17:39:14 +03:00
$chart->options['legend']['width'] = 105;
2022-06-09 15:01:19 +03:00
$chart->options['legend']['position'] = 'right';
2019-11-16 10:21:14 +03:00
return $chart;
}
public function show()
{
return view($this->views['show'])->with('class', $this);
}
public function print()
{
return view($this->views['print'])->with('class', $this);
}
public function export()
{
2022-06-01 10:15:55 +03:00
return ExportHelper::toExcel(new Export($this->views[$this->type], $this), $this->model->name);
2019-11-16 10:21:14 +03:00
}
2020-03-10 14:54:20 +03:00
public function setColumnWidth()
2020-03-09 17:17:42 +03:00
{
2020-06-12 13:38:58 +03:00
if (!$period = $this->getSetting('period')) {
2020-03-09 17:17:42 +03:00
return;
}
2020-03-10 14:54:20 +03:00
$width = '';
2020-03-09 17:17:42 +03:00
2020-06-12 13:38:58 +03:00
switch ($period) {
2020-03-09 17:17:42 +03:00
case 'quarterly':
2022-06-01 10:15:55 +03:00
$width = 'w-2/12 col-2';
2020-03-09 17:17:42 +03:00
break;
case 'yearly':
2022-06-01 10:15:55 +03:00
$width = 'w-4/12 col-4';
2020-03-09 17:17:42 +03:00
break;
case 'monthly':
2022-08-01 15:30:25 +03:00
$width = 'col-1 w-20';
break;
2020-03-09 17:17:42 +03:00
}
2020-03-10 14:54:20 +03:00
if (empty($width)) {
2020-03-09 17:17:42 +03:00
return;
}
2020-03-10 14:54:20 +03:00
$this->column_name_width = $this->column_value_width = $width;
2020-03-09 17:17:42 +03:00
}
public function setChartLabelFormatter()
{
if (count($this->tables) > 1) {
foreach ($this->tables as $table_key => $table) {
if (empty($this->chart[$table_key])) {
continue;
}
2022-10-24 14:53:05 +03:00
$this->chart[$table_key]['bar']['yaxis']['labels']['formatter'] = $this->getChartLabelFormatter($this->bar_formatter_type);
$this->chart[$table_key]['donut']['yaxis']['labels']['formatter'] = $this->getChartLabelFormatter($this->donut_formatter_type);
}
} else {
2022-10-24 14:53:05 +03:00
$this->chart['bar']['yaxis']['labels']['formatter'] = $this->getChartLabelFormatter($this->bar_formatter_type);
$this->chart['donut']['yaxis']['labels']['formatter'] = $this->getChartLabelFormatter($this->donut_formatter_type);
}
}
2019-11-16 10:21:14 +03:00
public function setYear()
{
2021-01-23 12:16:58 +03:00
$this->year = $this->getSearchStringValue('year', Date::now()->year);
2019-11-16 10:21:14 +03:00
}
public function setViews()
{
$this->views = [
2022-06-01 10:15:55 +03:00
'show' => 'components.reports.show',
'print' => 'components.reports.print',
'filter' => 'components.reports.filter',
'detail' => 'components.reports.detail',
'detail.content.header' => 'components.reports.detail.content.header',
'detail.content.footer' => 'components.reports.detail.content.footer',
'detail.table' => 'components.reports.detail.table',
'detail.table.header' => 'components.reports.detail.table.header',
'detail.table.body' => 'components.reports.detail.table.body',
'detail.table.row' => 'components.reports.detail.table.row',
'detail.table.footer' => 'components.reports.detail.table.footer',
'summary' => 'components.reports.summary',
'summary.content.header' => 'components.reports.summary.content.header',
'summary.content.footer' => 'components.reports.summary.content.footer',
'summary.table' => 'components.reports.summary.table',
'summary.table.header' => 'components.reports.summary.table.header',
'summary.table.body' => 'components.reports.summary.table.body',
'summary.table.row' => 'components.reports.summary.table.row',
'summary.table.footer' => 'components.reports.summary.table.footer',
'summary.chart' => 'components.reports.summary.chart',
2019-11-16 10:21:14 +03:00
];
}
public function setTables()
{
$this->tables = [
2022-06-01 10:15:55 +03:00
'default' => trans_choice('general.totals', 1),
2019-11-16 10:21:14 +03:00
];
}
public function setDates()
{
2022-06-01 10:15:55 +03:00
if (! $period = $this->getSetting('period')) {
2020-01-27 22:41:08 +03:00
return;
}
2020-06-12 13:38:58 +03:00
$function = 'sub' . ucfirst(str_replace('ly', '', $period));
2019-11-16 10:21:14 +03:00
2021-01-23 12:16:58 +03:00
$start = $this->getFinancialStart($this->year)->copy()->$function();
2019-11-16 10:21:14 +03:00
for ($j = 1; $j <= 12; $j++) {
2020-06-12 13:38:58 +03:00
switch ($period) {
2019-11-16 10:21:14 +03:00
case 'yearly':
$start->addYear();
$j += 11;
break;
case 'quarterly':
$start->addQuarter();
$j += 2;
break;
default:
$start->addMonth();
2021-02-05 21:00:15 +03:00
break;
}
2019-11-16 10:21:14 +03:00
2021-02-05 21:00:15 +03:00
$date = $this->getFormattedDate($start);
2019-11-16 10:21:14 +03:00
2021-02-05 21:00:15 +03:00
$this->dates[] = $date;
2019-11-16 10:21:14 +03:00
2022-06-01 10:15:55 +03:00
foreach ($this->tables as $table_key => $table_name) {
$this->footer_totals[$table_key][$date] = 0;
2019-11-16 10:21:14 +03:00
}
}
}
public function setFilters()
{
2020-01-31 12:59:12 +03:00
event(new FilterShowing($this));
2019-11-16 10:21:14 +03:00
}
public function setGroups()
{
2020-01-16 15:39:37 +03:00
$this->groups = [];
2020-01-31 12:59:12 +03:00
event(new GroupShowing($this));
2019-11-16 10:21:14 +03:00
}
public function setRows()
{
2020-01-31 12:59:12 +03:00
event(new RowsShowing($this));
2019-11-16 10:21:14 +03:00
}
2020-05-16 11:37:56 +03:00
public function setTotals($items, $date_field, $check_type = false, $table = 'default', $with_tax = true)
2019-11-16 10:21:14 +03:00
{
2021-07-16 11:26:17 +03:00
$group_field = $this->getSetting('group') . '_id';
2019-11-16 10:21:14 +03:00
foreach ($items as $item) {
// Make groups extensible
$item = $this->applyGroups($item);
$date = $this->getFormattedDate(Date::parse($item->$date_field));
2021-07-16 11:26:17 +03:00
if (!isset($item->$group_field)) {
continue;
}
$group = $item->$group_field;
2019-11-16 10:21:14 +03:00
2020-01-27 22:41:08 +03:00
if (
2021-07-16 11:26:17 +03:00
!isset($this->row_values[$table][$group])
|| !isset($this->row_values[$table][$group][$date])
2020-02-21 18:03:23 +03:00
|| !isset($this->footer_totals[$table][$date])
) {
2019-11-16 10:21:14 +03:00
continue;
}
2020-05-16 11:37:56 +03:00
$amount = $item->getAmountConvertedToDefault(false, $with_tax);
2019-11-16 10:21:14 +03:00
2020-12-24 01:28:38 +03:00
$type = ($item->type === Document::INVOICE_TYPE || $item->type === 'income') ? 'income' : 'expense';
2019-11-16 10:21:14 +03:00
2020-01-27 22:41:08 +03:00
if (($check_type == false) || ($type == 'income')) {
2021-07-16 11:26:17 +03:00
$this->row_values[$table][$group][$date] += $amount;
2019-11-16 10:21:14 +03:00
2020-01-27 22:41:08 +03:00
$this->footer_totals[$table][$date] += $amount;
} else {
2021-07-16 11:26:17 +03:00
$this->row_values[$table][$group][$date] -= $amount;
2019-11-16 10:21:14 +03:00
2020-01-27 22:41:08 +03:00
$this->footer_totals[$table][$date] -= $amount;
2019-11-16 10:21:14 +03:00
}
}
}
2021-07-16 11:26:17 +03:00
public function setArithmeticTotals($items, $date_field, $operator = 'add', $table = 'default', $amount_field = 'amount')
{
$group_field = $this->getSetting('group') . '_id';
$function = $operator . 'ArithmeticAmount';
foreach ($items as $item) {
// Make groups extensible
$item = $this->applyGroups($item);
$date = $this->getFormattedDate(Date::parse($item->$date_field));
if (!isset($item->$group_field)) {
continue;
}
$group = $item->$group_field;
if (
!isset($this->row_values[$table][$group])
|| !isset($this->row_values[$table][$group][$date])
|| !isset($this->footer_totals[$table][$date])
) {
continue;
}
$amount = isset($item->$amount_field) ? $item->$amount_field : 1;
$this->$function($this->row_values[$table][$group][$date], $amount);
$this->$function($this->footer_totals[$table][$date], $amount);
}
}
public function addArithmeticAmount(&$current, $amount)
{
$current = $current + $amount;
}
public function subArithmeticAmount(&$current, $amount)
{
$current = $current - $amount;
}
public function mulArithmeticAmount(&$current, $amount)
{
$current = $current * $amount;
}
public function divArithmeticAmount(&$current, $amount)
{
$current = $current / $amount;
}
public function modArithmeticAmount(&$current, $amount)
{
$current = $current % $amount;
}
public function expArithmeticAmount(&$current, $amount)
{
$current = $current ** $amount;
}
2019-11-16 10:21:14 +03:00
public function applyFilters($model, $args = [])
{
2020-01-31 12:59:12 +03:00
event(new FilterApplying($this, $model, $args));
2019-11-16 10:21:14 +03:00
return $model;
}
public function applyGroups($model, $args = [])
{
2020-01-31 12:59:12 +03:00
event(new GroupApplying($this, $model, $args));
2019-11-16 10:21:14 +03:00
return $model;
}
public function getFormattedDate($date)
{
$formatted_date = null;
2020-06-12 13:38:58 +03:00
switch ($this->getSetting('period')) {
2019-11-16 10:21:14 +03:00
case 'yearly':
$financial_year = $this->getFinancialYear($this->year);
if ($date->greaterThanOrEqualTo($financial_year->getStartDate()) && $date->lessThanOrEqualTo($financial_year->getEndDate())) {
2021-02-12 23:05:50 +03:00
if (setting('localisation.financial_denote') == 'begins') {
$formatted_date = $financial_year->getStartDate()->copy()->format($this->getYearlyDateFormat());
} else {
$formatted_date = $financial_year->getEndDate()->copy()->format($this->getYearlyDateFormat());
}
}
2019-11-16 10:21:14 +03:00
break;
case 'quarterly':
2021-02-08 12:07:22 +03:00
$quarters = $this->getFinancialQuarters($this->year);
foreach ($quarters as $quarter) {
if ($date->lessThan($quarter->getStartDate()) || $date->greaterThan($quarter->getEndDate())) {
continue;
}
$start = $quarter->getStartDate()->format($this->getQuarterlyDateFormat($this->year));
$end = $quarter->getEndDate()->format($this->getQuarterlyDateFormat($this->year));
$formatted_date = $start . '-' . $end;
}
2019-11-16 10:21:14 +03:00
break;
default:
$formatted_date = $date->copy()->format($this->getMonthlyDateFormat($this->year));
2019-11-16 10:21:14 +03:00
break;
}
return $formatted_date;
2019-11-16 10:21:14 +03:00
}
public function getUrl($action = 'print')
{
$url = company_id() . '/common/reports/' . $this->model->id . '/' . $action;
2019-11-16 10:21:14 +03:00
2021-01-23 12:16:58 +03:00
$search = request('search');
2019-11-16 10:21:14 +03:00
2021-01-23 12:16:58 +03:00
if (!empty($search)) {
$url .= '?search=' . $search;
}
2019-11-16 10:21:14 +03:00
2021-01-23 12:16:58 +03:00
return $url;
2019-11-16 10:21:14 +03:00
}
2020-01-16 15:39:37 +03:00
2020-06-12 13:38:58 +03:00
public function getSetting($name, $default = '')
{
return $this->model->settings->$name ?? $default;
}
2022-06-01 10:15:55 +03:00
public function getBasis()
{
return $this->getSearchStringValue('basis', $this->getSetting('basis'));
}
2020-01-16 15:39:37 +03:00
public function getFields()
{
return [
$this->getGroupField(),
$this->getPeriodField(),
$this->getBasisField(),
];
}
public function getGroupField()
{
$this->setGroups();
return [
2022-06-01 10:15:55 +03:00
'type' => 'select',
2020-01-16 15:39:37 +03:00
'name' => 'group',
'title' => trans('general.group_by'),
'icon' => 'folder',
'values' => $this->groups,
'selected' => 'category',
'attributes' => [
'required' => 'required',
],
];
}
public function getPeriodField()
{
return [
2022-06-01 10:15:55 +03:00
'type' => 'select',
2020-01-16 15:39:37 +03:00
'name' => 'period',
'title' => trans('general.period'),
'icon' => 'calendar',
'values' => [
'monthly' => trans('general.monthly'),
'quarterly' => trans('general.quarterly'),
'yearly' => trans('general.yearly'),
],
'selected' => 'quarterly',
'attributes' => [
'required' => 'required',
],
];
}
public function getBasisField()
{
return [
2022-06-01 10:15:55 +03:00
'type' => 'select',
2020-01-16 15:39:37 +03:00
'name' => 'basis',
'title' => trans('general.basis'),
'icon' => 'file',
'values' => [
'accrual' => trans('general.accrual'),
'cash' => trans('general.cash'),
],
'selected' => 'accrual',
'attributes' => [
'required' => 'required',
],
];
}
public function randHexColorPart(): string
{
return str_pad( dechex( mt_rand( 0, 255 ) ), 2, '0', STR_PAD_LEFT);
}
public function randHexColor(): string
{
return '#' . $this->randHexColorPart() . $this->randHexColorPart() . $this->randHexColorPart();
}
2019-11-16 10:21:14 +03:00
}