I was able to create an acceptable income statement with the attached view. I needed to install the the Views Raw SQL module and Views Aggregator Plus module.

$view = new view();
$view->name = 'income_statement';
$view->description = '';
$view->tag = 'default';
$view->base_table = 'ledger_account_entry';
$view->human_name = 'income statement';
$view->core = 7;
$view->api_version = '3.0';
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */

/* Display: Master */
$handler = $view->new_display('default', 'Master', 'default');
$handler->display->display_options['title'] = 'income statement';
$handler->display->display_options['use_more_always'] = FALSE;
$handler->display->display_options['access']['type'] = 'none';
$handler->display->display_options['cache']['type'] = 'none';
$handler->display->display_options['query']['type'] = 'views_query';
$handler->display->display_options['exposed_form']['type'] = 'basic';
$handler->display->display_options['pager']['type'] = 'none';
$handler->display->display_options['pager']['options']['offset'] = '0';
$handler->display->display_options['style_plugin'] = 'views_aggregator';
$handler->display->display_options['style_options']['columns'] = array(
'raw_field_1' => 'raw_field_1',
'type' => 'type',
'name' => 'name',
'raw_field' => 'raw_field',
);
$handler->display->display_options['style_options']['default'] = 'type';
$handler->display->display_options['style_options']['info'] = array(
'raw_field_1' => array(
'sortable' => 0,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
'has_aggr' => 0,
'aggr' => array(
'views_aggregator_first' => 'views_aggregator_first',
),
'aggr_par' => '',
'has_aggr_column' => 0,
'aggr_column' => 'views_aggregator_sum',
'aggr_par_column' => '',
),
'type' => array(
'sortable' => 0,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
'has_aggr' => 0,
'aggr' => array(
'views_aggregator_first' => 'views_aggregator_first',
),
'aggr_par' => '',
'has_aggr_column' => 0,
'aggr_column' => 'views_aggregator_sum',
'aggr_par_column' => '',
),
'name' => array(
'sortable' => 0,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
'has_aggr' => 1,
'aggr' => array(
'views_aggregator_group_and_compress' => 'views_aggregator_group_and_compress',
),
'aggr_par' => '',
'has_aggr_column' => 0,
'aggr_column' => 'views_aggregator_sum',
'aggr_par_column' => '',
),
'raw_field' => array(
'sortable' => 0,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
'has_aggr' => 1,
'aggr' => array(
'views_aggregator_sum' => 'views_aggregator_sum',
),
'aggr_par' => '',
'has_aggr_column' => 1,
'aggr_column' => 'views_aggregator_sum',
'aggr_par_column' => 'Net Income',
),
);
$handler->display->display_options['style_options']['caption'] = 'Income Statement for the year';
$handler->display->display_options['style_options']['totals_per_page'] = '1';
$handler->display->display_options['style_options']['totals_row_position'] = array(
1 => 0,
2 => '2',
);
/* Relationship: Account entry: Account aid */
$handler->display->display_options['relationships']['account']['id'] = 'account';
$handler->display->display_options['relationships']['account']['table'] = 'ledger_account_entry';
$handler->display->display_options['relationships']['account']['field'] = 'account';
$handler->display->display_options['relationships']['account']['required'] = TRUE;
$handler->display->display_options['relationships']['account']['bundle_types'] = array(
'income' => 'income',
'expenses' => 'expenses',
);
/* Relationship: Account entry: Transaction tid */
$handler->display->display_options['relationships']['transaction']['id'] = 'transaction';
$handler->display->display_options['relationships']['transaction']['table'] = 'ledger_account_entry';
$handler->display->display_options['relationships']['transaction']['field'] = 'transaction';
$handler->display->display_options['relationships']['transaction']['required'] = TRUE;
/* Field: Raw SQL: Raw field */
$handler->display->display_options['fields']['raw_field_1']['id'] = 'raw_field_1';
$handler->display->display_options['fields']['raw_field_1']['table'] = 'views_raw_sql';
$handler->display->display_options['fields']['raw_field_1']['field'] = 'raw_field';
$handler->display->display_options['fields']['raw_field_1']['exclude'] = TRUE;
$handler->display->display_options['fields']['raw_field_1']['raw_sql'] = 'ledger_account_entry.eid';
/* Field: Account: Account type */
$handler->display->display_options['fields']['type']['id'] = 'type';
$handler->display->display_options['fields']['type']['table'] = 'ledger_account';
$handler->display->display_options['fields']['type']['field'] = 'type';
$handler->display->display_options['fields']['type']['relationship'] = 'account';
/* Field: Account: Name */
$handler->display->display_options['fields']['name']['id'] = 'name';
$handler->display->display_options['fields']['name']['table'] = 'ledger_account';
$handler->display->display_options['fields']['name']['field'] = 'name';
/* Field: Raw SQL: Raw field */
$handler->display->display_options['fields']['raw_field']['id'] = 'raw_field';
$handler->display->display_options['fields']['raw_field']['table'] = 'views_raw_sql';
$handler->display->display_options['fields']['raw_field']['field'] = 'raw_field';
$handler->display->display_options['fields']['raw_field']['group_type'] = 'sum';
$handler->display->display_options['fields']['raw_field']['label'] = 'Balance';
$handler->display->display_options['fields']['raw_field']['raw_sql'] = '(Select round(-1*ledger_value_numerator/ledger_value_denominator,2) from field_data_ledger_value f inner join ledger_account_entry ae on f.entity_id=ae.eid WHERE ae.eid=raw_sql_field)';
/* Filter criterion: Date: Date (ledger_transaction) */
$handler->display->display_options['filters']['date_filter']['id'] = 'date_filter';
$handler->display->display_options['filters']['date_filter']['table'] = 'ledger_transaction';
$handler->display->display_options['filters']['date_filter']['field'] = 'date_filter';
$handler->display->display_options['filters']['date_filter']['relationship'] = 'transaction';
$handler->display->display_options['filters']['date_filter']['value']['value'] = '2014';
$handler->display->display_options['filters']['date_filter']['exposed'] = TRUE;
$handler->display->display_options['filters']['date_filter']['expose']['operator_id'] = 'date_filter_op';
$handler->display->display_options['filters']['date_filter']['expose']['label'] = 'Date (ledger_transaction)';
$handler->display->display_options['filters']['date_filter']['expose']['operator'] = 'date_filter_op';
$handler->display->display_options['filters']['date_filter']['expose']['identifier'] = 'date_filter';
$handler->display->display_options['filters']['date_filter']['expose']['remember_roles'] = array(
2 => '2',
1 => 0,
3 => 0,
4 => 0,
5 => 0,
6 => 0,
);
$handler->display->display_options['filters']['date_filter']['granularity'] = 'year';
$handler->display->display_options['filters']['date_filter']['year_range'] = '-3:+0';
$handler->display->display_options['filters']['date_filter']['date_fields'] = array(
'ledger_transaction.timestamp' => 'ledger_transaction.timestamp',
);

/* Display: Page */
$handler = $view->new_display('page', 'Page', 'page');
$handler->display->display_options['path'] = 'income-statement';

Comments

Anonymous’s picture

Nice view. Thanks.

Is it on purpose that the view only displays accounts with either income or expense in them, and does not display their parent accounts?
If an account has a parent, shouldn't it show all accounts up to the top hierarchy level, so that the position of the account in question in the hierarchy is at least visible?

Beakerboy’s picture

If you have an improvement to the view, go ahead and post it. I said it was an "acceptable" income statement, but not that it was perfect :-)

Thinking back, I think I wasn't able to figure out how to do that with the views module, Views Raw SQL module, and Views Aggregator Plus module. There has to be a way to make it more detailed using a raw SQL statement within the Ledger module, but I didn't have time to figure out how to do that.

Beakerboy’s picture

Michael,
What would your preferred location be for reports like this?
/ledger/book/%/income-statement
/ledger/book/%/reports/income-statement
/ledger/reports/income-statement/%book

Thanks,
Kevin

m.stenta’s picture

@Beakerboy: Good question.

I think of the three, I like "/ledger/book/%/reports/income-statement" the best. Maybe even simpler: "/ledger/book/%/reports/income"?

m.stenta’s picture

@Beakerboy: I haven't tested this View yet - I really should. Could you describe a bit about how it makes use of the Views Raw SQL and Views Aggregator Plus modules? I'm wondering if we could achieve the same thing with custom Views handlers - assuming the needs are pretty straightforward. I have a pretty good understanding of Views API so I can help formulate whatever's needed. I'll try to make some time to test this out soon.

Beakerboy’s picture

@m.stenta

The Raw SQL is to perform a inner select, due to the fraction class:
SELECT [a bunch of stuff], (SELECT round(-1*ledger_value_numerator/ledger_value_denominator,2) FROM field_data_ledger_value f INNER JOIN ledger_account_entry ae ON f.entity_id=ae.eid WHERE ae.eid=ledger_account_entry.eid) as raw_sql_field FROM ...

The Views Aggregator is to group by and sum the account entries.

There probably is a way to do it with handler, but I know nothing about that. This is the only way I could find to get it to work by creating a View within the Views UI.
I sent you a message about a Ledger Reports module which contains the balance sheet and income statement view exports. If you would like me to push that to the repository so you can make some modifications, that would be great.
Kevin

m.stenta’s picture

@Beakerboy: I installed the Views Aggregator and Views Raw SQL modules and imported this View. Pretty awesome start!

The more I think about it, though, the more I'm worried that Views might not be the best way to do an Income Statement. It might make more sense to create it using PHP, along with a theme template and preprocess function. I have a feeling trying to do the whole thing in SQL will be painful. But maybe I'm wrong. And I'm wary of adding more dependencies at this stage - with Drupal 8 officially released - the sooner we can get onto D8 the better prepared we'll be for the future...

I didn't receive a message about the Ledger Reports module. Did you send it via drupal.org? (was it recently?)

Beakerboy’s picture

@m.stenta
I sent a message via the "contact" link on your drupal.org profile page yesterday. I basically asked how you would like a Ledger Reports module, grant me push privs on git, or should I make a patch.

Explain what you mean by SQL vs PHP....Do you mean the rawSQL and Aggregator+? Like I said, this was the only way I could get the report using the views UI. There has to be a way to just type in the SQL statement, and format the data for display without using these extra plug-ins...or maybe even without using views...maybe?

I just think...long term...you'll probably want a Reports module, so I put what I had in one which can be used as a stepping stone. It will also need a cash-flow statement, an equity statement, and maybe even month-on-month profit/loss tables or graphs. Right now, the income statement is based off a calendar year. Down the road it will have to look at the accounting period instead.

Also let me know what path structure you would like on these.

m.stenta’s picture

@Beakerboy: that's weird! I didn't receive your message. Maybe d.o messages are backed up? Hopefully I didn't lose it in a spam folder.

Explain what you mean by SQL vs PHP

I mean it might make sense to build reports WITHOUT Views. Just using PHP logic and a template perhaps. It may actually be easier to get what we want in the long run with that, as opposed to Views.

I like the idea of starting a separate module called Ledger Reports (http://drupal.org/project/ledger_reports) and getting started on it in there. Maybe we can think about pulling it into Ledger core once all the necessary reports are there, and any kinks are worked out.

Do you have permission to create drupal.org projects? I say go ahead and create one for Ledger Reports and we can start there. I'll link to it from the Ledger project page. This View is a start at least (and the balance sheet one - though I haven't looked at that yet), and we can decide if it makes sense to stick with Views, or to take a more customized PHP approach... what do you think?

m.stenta’s picture

At the very least, you can start a sandbox - then we can work on it together via Git.

Beakerboy’s picture

@m.stenta
I created a sandbox project, added my existing code, added you as a maintainer, and added a few issues.
Have fun!

Beakerboy’s picture

@m.stenta The sandbox project now has this report completely moved out of the Views system and into a raw db_query() statement. Accounts are totalized post SQL and the results are formatted in a generic drupal table. I've included a year select form, and contextual filters for book id and year. Now I have to figure out how to make the table look nice by adding some padding and horizontal lines between sections.

@andreas-speck I'll look at organizing by and adding totals on the parent accounts as well.

Let me know what you think.

m.stenta’s picture

Status: Active » Closed (won't fix)

@Beakerboy's Ledger Reports module was promoted to a full project: https://www.drupal.org/project/ledger_reports

I'm going to close this issue. Future discussion can be done in that module's queue.

@Beakerboy: do you want to add a note to the Ledger Reports project page that it is made for use with Ledger 7.x-1.x? As I get into 8.x-2.x development, you and I should talk about how we can adapt the reports - and/or if it makes sense for them to be included in Ledger itself.