[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
PNL report grouped by week/month/quarter/year
- Subject: PNL report grouped by week/month/quarter/year
- From: John Locke <..hidden..>
- Date: Thu, 09 Jan 2014 17:21:54 -0800
Hi,
I really, really needed a report that gave me a month-by-month cash flow
(profit and loss) summary for the past two years. And given that I can
do it much quicker in PHP and have a Drupal instance set up that already
talks to my LSMB database, I implemented it there.
The core SQL I took out of pnl__income_statement_cash , stripping out
the business unit and year-end options. Mainly just added a field
populated with date_trunc. I'm sure this could be added back to the
stored function with a new parameter.
In any case, here's the code I wrote. Not pretty, but got the job done
for me pretty quickly... Would be great to get something like this in LSMB.
I would think the first thing would be to modify
pnl__income_statement_cash and pnl__income_statement_accrual with one
more parameter, to specify a grouping period. Maybe some other reports,
too...
/**
* Helper function to get report data from LSMB 14
*
* @param string $dtstart Start date range
* @param string $dtend End of date range
* @param string $groupperiod One of null, 'week', 'month', 'quarter',
'year'
*
* @return object with many attached arrays. Following keys are set:
* -> cols Array of columns. Each column is an object containing:
* -> key used in data array
* -> label
* -> expense total for this column
* -> income total for this column
* -> total income - expense for this column
* -> rows Array of rows. Each row is an object containing:
* -> key used in data array
* -> label -- name of account
* -> link -- link to use for account
* -> accno -- Account Number
* -> category -- I or E
* -> expense -- total for this row
* -> income -- total for this row
* -> total income - expense for all rows/columns
* -> data array of arrays. Outer array = row, inner array = column
*/
function auriga_dashboard_pnl_cash($dtstart= NULL, $dtend = NULL,
$groupperiod = NULL) {
$params = array();
$query = "SELECT a.id, a.accno, a.description, a.category, ah.id as
header_id, ah.accno as header_ac
cno,
ah.description as header_description, ";
if ($groupperiod) {
$query .= "date_trunc(:groupperiod, ac.transdate) AS transperiod, ";
$params[':groupperiod'] = $groupperiod;
}
$query .= " CASE WHEN a.category = 'E' THEN -1 ELSE 1 END *
sum(ac.amount) AS amount,
at.path
FROM account a
JOIN account_heading ah on a.heading = ah.id
JOIN acc_trans ac ON a.id = ac.chart_id AND ac.approved
JOIN tx_report gl ON ac.trans_id = gl.id AND gl.approved
JOIN account_heading_tree at ON a.heading = at.id
WHERE ac.approved is true ";
if ($dtstart) {
$query .= " AND (ac.transdate >= :dtstart) ";
$params[':dtstart'] = $dtstart;
}
if ($dtend) {
$query .= " AND (ac.transdate <= :dtend) ";
$params[':dtend'] = $dtend;
}
$query .= " AND a.category IN ('I', 'E')
GROUP BY a.id, a.accno, ";
if ($groupperiod) {
$query .= "transperiod, ";
}
$query .= " a.description, a.category,
ah.id, ah.accno, ah.description, at.path
ORDER BY a.category DESC, a.accno ASC ";
if ($groupperiod) {
$query .= ", transperiod";
}
db_set_active('lsmb14');
$result = db_query($query, $params);
$items = $result->fetchAll();
db_set_active();
// now let's build our arrays...
$cols = array();
$rows = array();
$data = array();
$total = 0;
foreach ($items as $item) {
$type = $item->category == 'I' ? 'income' : 'expense';
if (empty($rows[$item->accno])) {
$rows[$item->accno] = array(
'key' => $item->accno,
'label' => $item->description,
'accno' => $item->accno,
'link' => $item->path,
'category' => $item->category,
'expense' => 0,
'income' => 0,
);
}
$rows[$item->accno][$type] += $item->amount;
$perioddate = strtotime($item->transperiod);
switch ($groupperiod) {
case 'week':
$period = date('Y',$perioddate).' Week '.date('W', $perioddate);
break;
case 'month':
$period = date('M',$perioddate).' '.date('Y', $perioddate);
break;
case 'year':
$period = date('Y',$perioddate);
break;
case 'quarter':
$period = date('Y',$perioddate).' Q' .ceil(date('m', $perioddate)/3);
break;
default:
$period = $dtstart . ' - ' . $dtend;
}
if (empty($cols[$item->transperiod])) {
$cols[$item->transperiod] = array(
'key' => $item->transperiod,
'label' => $period,
'expense' => 0,
'income' => 0,
'total' => 0,
);
}
$cols[$item->transperiod][$type] += $item->amount;
$data[$item->accno][$item->transperiod] = $item->amount;
}
// Now calculate column totals...
foreach ($cols as $k=>$col) {
$cols[$k]['total'] = $col['income'] - $col['expense'];
$total += $cols[$k]['total'];
}
$ret = new stdClass();
$ret->rows = $rows;
$ret->cols = $cols;
$ret->total = $total;
$ret->data = $data;
// TODO: Move the following to a theme function, and return $ret
$out = '<h2>Profit and Loss, cash basis</h2>';
$out .= '<h3>'.$dtstart.' - '.$dtend.'</h3>';
$out .= '<table class="timereport">';
$out .= '<thead>';
$out .= '<tr>';
$out .= '<th>Account #</th><th>Description</th>';
foreach ($ret->cols as $col) {
$out .= '<th>'.$col['label'].'</th>';
}
if (count($ret->cols) > 1) {
$out .= '<th>Total</th>';
}
$out .= '</tr>';
$out .= '</thead>';
$out .= '<tbody>';
// flag to get an income subtotal
$in_income = true;
foreach ($ret->rows as $r=>$row) {
if ($in_income && $row['category'] == 'E') {
// we are no longer in income, do a subtotal row
$in_income = false;
$out .= '<tr><th></th><th>Subtotal of Income:</th>';
foreach ($ret->cols as $k=>$col) {
$out .= '<th>' . $col['income'] . '</th>';
}
$out .= '</tr>';
}
$out .= '<tr><td>' . $row['accno'] . '</td><td>'.
$row['label'].'</td>';
foreach ($ret->cols as $k=>$col) {
$out .= '<td>';
$out .= isset($ret->data[$r][$k]) ? $ret->data[$r][$k] : 0;
$out .= '</td>';
}
$out .= '</tr>';
}
$out .= '<tr><th></th><th>Subtotal of Expenses:</th>';
foreach ($ret->cols as $k=>$col) {
$out .= '<th>' . $col['expense'] . '</th>';
}
$out .= '</tr>';
$out .= '<tr><th></th><th>Totals, income less expenses:</th>';
foreach ($ret->cols as $k=>$col) {
$out .= '<th>' . $col['total'] . '</th>';
}
$out .= '</tr>';
$out .= '</tbody>';
$out .= '</table>';
return $out;
}
Cheers,
John Locke
http://www.freelock.com