$category) { $row = reportGridRow($grid, $cat); $lines[] = [ 'kind' => $kind, 'cat' => $cat, 'name' => $category['category_name'], 'archived' => !empty($category['category_archived_at']), 'cells' => reportSumColumns($row['months'], $columns), 'total' => $row['total'], 'prev' => reportGridRow($prev_grid, $cat)['total'], ]; } return $lines; }; // A category with nothing in either year is left off - a statement lists what happened $active = function ($line) { return abs($line['total']) >= 0.005 || abs($line['prev']) >= 0.005; }; $income_lines = array_values(array_filter($category_lines($income_categories, $income, $income_prev, 'income'), $active)); $expense_lines = array_values(array_filter($category_lines($expense_categories, $expenses, $expenses_prev, 'expense'), $active)); $income_line = ['kind' => 'income_total', 'name' => 'Total income', 'cells' => reportSumColumns($income_total['months'], $columns), 'total' => $income_total['total'], 'prev' => $income_prev_total]; $expense_line = ['kind' => 'expense_total', 'name' => 'Total expenses', 'cells' => reportSumColumns($expense_total['months'], $columns), 'total' => $expense_total['total'], 'prev' => $expense_prev_total]; $profit_cells = []; foreach ($columns as $i => $column) { $profit_cells[$i] = $income_line['cells'][$i] - $expense_line['cells'][$i]; } $profit_line = ['kind' => 'profit', 'name' => 'Net profit', 'cells' => $profit_cells, 'total' => $profit, 'prev' => $profit_prev]; if (reportExporting()) { $csv_columns = ['section' => ['label' => 'Section'], 'name' => ['label' => 'Category']]; foreach ($columns as $i => [$label]) { $csv_columns["c$i"] = ['label' => $label, 'type' => 'money']; } $csv_columns['total'] = ['label' => $current_year ? "$year to date" : "$year total", 'type' => 'money']; $csv_columns['prev'] = ['label' => $current_year ? 'Same time last year' : (string) ($year - 1), 'type' => 'money']; $flatten = function ($line, $section) use ($columns) { $row = ['section' => $section, 'name' => $line['name'], 'total' => $line['total'], 'prev' => $line['prev']]; foreach ($columns as $i => $column) { $row["c$i"] = $line['cells'][$i]; } return $row; }; $csv_rows = []; foreach ($income_lines as $line) { $csv_rows[] = $flatten($line, 'Income'); } $csv_rows[] = $flatten($income_line, 'Income'); foreach ($expense_lines as $line) { $csv_rows[] = $flatten($line, 'Expenses'); } $csv_rows[] = $flatten($expense_line, 'Expenses'); $csv_rows[] = $flatten($profit_line, 'Net profit'); reportCsv("Profit and Loss $year", $csv_columns, $csv_rows); } // The chart goes a month at a time, whichever way the table is split $months_shown = $current_year ? intval(date('n')) : 12; $chart = ['income' => [], 'expenses' => [], 'profit' => []]; for ($m = 1; $m <= 12; $m++) { $in = round($income_total['months'][$m], 2); $out = round($expense_total['months'][$m], 2); $chart['income'][] = $m <= $months_shown ? $in : null; $chart['expenses'][] = $m <= $months_shown ? $out : null; $chart['profit'][] = $m <= $months_shown ? round($in - $out, 2) : null; } require_once "includes/inc_report_layout.php"; // One line of the statement $render = function ($line, $class = '') use ($columns, $year) { $kind = $line['kind']; $cat = $line['cat'] ?? 0; $list = $kind === 'income' ? 'income.php' : ($kind === 'expense' ? 'expenses.php' : ''); $html = '' . escapeHtml($line['name']) . (!empty($line['archived']) ? ' Archived' : '') . ''; foreach ($columns as $i => [$label, $first, $last]) { $url = ''; if ($list && ($cat || $kind === 'income')) { [$dtf, $dtt] = reportColumnDates($year, $first, $last); $url = "/agent/$list?" . ($cat ? "category=$cat" : 'show=uncategorised') . "&canned_date=custom&dtf=$dtf&dtt=$dtt"; } $amount = $line['cells'][$i]; $html .= '' . reportGridAmount($amount, $url) . ''; } // Spending going up is not good news $good = in_array($kind, ['expense', 'expense_total'], true) ? 'down' : 'up'; $html .= '' . reportGridAmount($line['total']) . '' . '' . reportGridAmount($line['prev']) . '' . '' . (reportChange($line['total'], $line['prev']) === null ? '–' : reportChangeHtml($line['total'], $line['prev'], $good)) . '' . ''; return $html; }; $span = count($columns) + 4; ?>
[$year, $years], 'controls' => reportSegmentControl('view', $view, ['month' => 'Monthly', 'quarter' => 'Quarterly']), 'subtitle' => $current_year ? "$year to date" : (string) $year, 'print_meta' => $current_year ? "January 1 to " . date('F j, Y') : "Year $year", ]); ?> 'Income', 'value' => reportMoney($income_total['total']), 'sub' => reportChangeHtml($income_total['total'], $income_prev_total, 'up', $compare), 'url' => "/agent/income.php?canned_date=custom&dtf=$from&dtt=$to"], ['label' => 'Expenses', 'value' => reportMoney($expense_total['total']), 'sub' => reportChangeHtml($expense_total['total'], $expense_prev_total, 'down', $compare), 'url' => "/agent/expenses.php?canned_date=custom&dtf=$from&dtt=$to"], ['label' => 'Net profit', 'value' => reportMoney($profit), 'tone' => $profit < 0 ? 'danger' : '', 'sub' => reportChangeHtml($profit, $profit_prev, 'up', $compare)], ['label' => 'Profit margin', 'value' => reportPercent($margin), 'tone' => $margin !== null && $margin < 0 ? 'danger' : '', 'sub' => $margin_prev === null ? 'Nothing before to compare' : escapeHtml(reportPercent($margin_prev)) . ' ' . escapeHtml($current_year ? 'same time last year' : 'in ' . ($year - 1)) . ''], ]) ?>
'bar', 'labels' => array_map(function ($m) { return date('M', mktime(0, 0, 0, $m, 1)); }, range(1, 12)), 'series' => [ ['label' => 'Income', 'data' => $chart['income'], 'tone' => 'success'], ['label' => 'Expenses', 'data' => $chart['expenses'], 'tone' => 'danger'], ['label' => 'Net profit', 'data' => $chart['profit'], 'tone' => 'accent', 'type' => 'line'], ], 'format' => 'money', ], 'lg', 'Income, expenses and net profit per month') ?>
$column) { $cell_margin = reportShare($profit_line['cells'][$i], $income_line['cells'][$i], 1); ?>
Category title=" – "> Last year Change
Income
No income this year or last.
Expenses
No spending this year or last.
Margin –' : reportPercent($cell_margin) ?>