DBIx-Knowledge

 view release on metacpan or  search on metacpan

lib/DBIx/Knowledge/Report.pm  view on Meta::CPAN

#
# $Id: Report.pm,v 1.12 2006/04/17 00:19:04 dennisl Exp $
#


package DBIx::Knowledge::Report;

use vars qw(%allowed_crit_fields %np_allowed_crit_fields);

use strict;
use CGI::AutoForm;
use DBIx::IO::Search;
use DBIx::IO::GenLib ();
use POSIX ();
use DBIx::Knowledge;
use DBIx::Knowledge::Output;
use CGI::CRUD::TableIO;

use constant OK => 0;

$DBIx::Knowledge::Report::REPORT_SELECT_LIST_TABLE_NAME = 'dbix_knowledge_data_point';

my $potential_select_fields;
##at anyway to make this readonly?
my %potential_select_fields_by_id;
##at anyway to make this readonly?
my %potential_select_fields_by_order_legend;

my $title_field = {
    FIELD_NAME => 'REPORT_TITLE',
    INPUT_CONTROL_TYPE => 'TEXT',
    SEARCH_CONTROL_TYPE => 'TEXT',
    HEADING => 'Report title',
    DATATYPE => 'CHAR',
    REQUIRED => 'N',
    INSERTABLE => 'Y',
    SEARCHABLE => 'Y',
    HELP_SUMMARY => 'Title of this report',
};

# I've been working on some Business Intelligence libraries for data sets that exhibit Linear Convergence and was able to acheive such a convergence temporally (over an axis of time) for much of the data in OP with the help of some key data aggregati...
##at should use NULLIF(poa.preopt_priority,0), NULLIF(poa.opt_priority_override,0), NULLIF(o.opt_priority_override,0), etc...
my $select_field = {
    FIELD_NAME => 'SELECT_FIELD',
    INPUT_CONTROL_TYPE => 'SELECT',
    SEARCH_CONTROL_TYPE => 'SELECT',
    SEARCH_MULT_SELECT => 7,
    HEADING => 'Report on',
    DATATYPE => 'CHAR',
    REQUIRED => 'Y',
    INSERTABLE => 'Y',
    SEARCHABLE => 'Y',
    HELP_SUMMARY => <<EOH,
Choose the data points to include
in this report.
EOH
};

##at anyway to make this readonly?
my @select_field_picklist = ();
##at anyway to make this readonly?
my @np_select_field_picklist = ();

my $subtotal_field = {
    FIELD_NAME => 'SUBTOTAL_FIELD',
    INPUT_CONTROL_TYPE => 'SELECT',
    SEARCH_CONTROL_TYPE => 'SELECT',
    SEARCH_MULT_SELECT => 4,
    HEADING => 'Summary totals on',
    DATATYPE => 'CHAR',
    REQUIRED => 'N',
    INSERTABLE => 'Y',
    SEARCHABLE => 'Y',
    HELP_SUMMARY => <<EOH,
Choose which data points will have
subtotal summary lines. Must be a
subset of the fields selected above.
EOH
};

##at anyway to make this readonly?
my @subtotal_field_picklist;

##at anyway to make this readonly?
my @np_subtotal_field_picklist = ();

my $aux_info_field = {

lib/DBIx/Knowledge/Report.pm  view on Meta::CPAN

=item C<new> (constructor)

 $report = new DBIx::Knowledge::Report($crud_output, $report_table_name[, $data_points, $cache_list]);

Create a $report object where $crud_output is a L<CGI::CRUD> output object and $report_table_name
is the name of the table or view to be reported upon.
An optional array reference $data_points may be given as described in L<Data point definitions>,
otherwise you must invoke C<select_list_from_table()>.
Optional boolean $cache_list will cache the data point list.

Upon error, C<server_error()> will be called on $crud_output and undef will be returned.

=cut

sub new
{
    my $caller = shift;

    my ($output,$report_table_name,$potential_select_fields_in,$cache_list) = @_;

    my $self = {
        report_table_name => $report_table_name,
        output => $output,
    };

    create_select_list($potential_select_fields_in,$cache_list) if defined($potential_select_fields_in);

    my $class = ref($caller) || $caller;
    bless($self,$class);

    my $sqlclass = $self->_pull_driver($output->dbh());
    ($output->server_error(),return undef) unless defined($sqlclass);

    $self->{sql_gen} = $sqlclass->new();
    
    return $self;
}

sub _pull_driver
{
    my ($caller,$dbh) = @_;

    return $caller->{sqlclass} if ref($caller) && defined($caller->{sqlclass});

    # SQL classes must be named after the DBI driver name
    my $sqlclass = "DBIx::Knowledge::$dbh->{Driver}{Name}SQL";
    eval qq(require $sqlclass) || (warn("Database driver not supported"),return undef);

    $caller->{sqlclass} = $sqlclass if ref($caller);
    return $sqlclass;
}

sub create_select_list
{
    my ($potential_select_fields_in,$cache_list) = @_;
    return 1 if $cache_list && $potential_select_fields;

    $potential_select_fields = $potential_select_fields_in;

    %potential_select_fields_by_id = ();
    %potential_select_fields_by_order_legend = ();
    @select_field_picklist = ();
    @np_select_field_picklist = ();
    @subtotal_field_picklist = ();
    @np_subtotal_field_picklist = ();

    map { $potential_select_fields_by_id{$_->{ID}} = $_ } @$potential_select_fields;

    map { $potential_select_fields_by_order_legend{$_->{order_legend}} = $_ } @$potential_select_fields;
    @select_field_picklist = map({ ID => $_->{ID}, MASK => "$_->{header} ($_->{order_legend})" },@$potential_select_fields);
    map { push(@np_select_field_picklist,{ ID => $_->{ID}, MASK => ($_->{np_header} ? $_->{np_header} : $_->{header}) . ' ($_->{order_legend})' }) if $_->{np_allow} } @$potential_select_fields;
    map { push(@subtotal_field_picklist,
                {
                    ID => $_->{ID},
                    MASK => "$_->{header} ($_->{order_legend})"
                }
               )
            unless $_->{group_by_expr}
        }
        reverse @$potential_select_fields;
    push(@subtotal_field_picklist,{ ID => $DBIx::Knowledge::REPORT_TOTAL_KEY, MASK => 'Report grand total (GRANDTOT)' });
    map { push(@np_subtotal_field_picklist,
                {
                    ID => $_->{ID},
                    MASK => "$_->{header} ($_->{order_legend})"
                }
               )
            if $_->{np_allow} && !$_->{group_by_expr}
        }
        reverse @$potential_select_fields;
    push(@np_subtotal_field_picklist,{ ID => $DBIx::Knowledge::REPORT_TOTAL_KEY, MASK => 'Report grand total (GRANDTOT)' });
}

=pod

=item C<select_list_from_table>

 $bool = $report->select_list_from_table([$report_table_name, $cache_list, $dbh, $select_field_table_name]);

Retrieve the list of data points for report building from a database table.
Optional $report_table_name overrides the same parameter given to the constructor only for the purpose of identifying the group of datapoints by matching the C<TABLE_NAME>
column in the data points table. 
Boolean $cache_list will cache the data point list, $dbh is an appropriate DBI database handle.
The table that stores the data points may be given by
$select_field_table_name table, which defaults to $DBIx::Knowledge::Report::REPORT_SELECT_LIST_TABLE_NAME.

Upon error, C<server_error()> or C<perror> will be called on $crud_output and false will be returned, otherwise return true.

=cut

sub select_list_from_table
{
    my ($self,$report_table_name,$cache_list,$dbh,$select_field_table_name) = @_;
    return 1 if $cache_list && $potential_select_fields;

    my @select_list;

    $dbh ||= $self->{output}->dbh();
    $report_table_name ||= $self->{report_table_name};
    $select_field_table_name ||= $DBIx::Knowledge::Report::REPORT_SELECT_LIST_TABLE_NAME;

    my $searcher = new DBIx::IO::Search($dbh,$select_field_table_name);
    my $recs;
    if (ref($searcher))
    {
        $searcher->build_scalar_crit('TABLE_NAME','=',$report_table_name);
        $recs = $searcher->search(undef,[ 'APPEAR_ORDER' ]) || return undef;
    }
    elsif (!defined($searcher))
    {
        $self->{output}->server_error();
        return undef;
    }
    elsif (!$searcher)
    {
        warn("$report_table_name does not seem to exist so no select list could be found");
        return 1;
    }
    else
    {
        die("A horrible death");
    }

    foreach my $rec (@$recs)
    {

lib/DBIx/Knowledge/Report.pm  view on Meta::CPAN

        $r->output($form->prepare($q),($ENV{SMARTCRUDDY_FAST_TEMPLATE_MAIN} || 'smartcruddy.tpl'));
        return OK;
    }

    $q = $form->format_query($r->query());

    my $tq = $r->query();
    $r->{tpl_vars}{FULL_SAVED_QUERY} = CGI::CRUD::TableIO::stringify_query($tq);
    $tq = { %$tq };
    delete($tq->{'__SDAT_TAB_ACTION.ACTION'});
    $r->{tpl_vars}{CUSTOM_SAVED_QUERY} = CGI::CRUD::TableIO::stringify_query($tq);

    my $select_fields_q = $q->{FORMAT}{SELECT_FIELD};
    $select_fields_q = [ $select_fields_q ] unless ref($select_fields_q);
    my %select_fields_q;
    @select_fields_q{ @$select_fields_q } = (1) x @$select_fields_q;
    my @select_fields = ();

    if (length($r->{np_uname})) 
    {
        foreach my $item (@np_select_field_picklist) 
        {
            if ($select_fields_q{$item->{ID}}) 
            {
                my %f = %{$potential_select_fields_by_id{$item->{ID}}}; 
                # overwrite header with np_header for np report
                $f{header} = $f{np_header} if ($f{np_header}); 
                push(@select_fields, \%f);
            }
        }
    }
    else 
    {
        map { push(@select_fields,$potential_select_fields_by_id{$_->{ID}}) if $select_fields_q{$_->{ID}} } @select_field_picklist;
    }

    my $subtotal_fields_q = $q->{FORMAT}{SUBTOTAL_FIELD};
    my @subtotal_fields = ();
    if ($subtotal_fields_q)
    {
        $subtotal_fields_q = [ $subtotal_fields_q ] unless ref($subtotal_fields_q);
        my %subtotal_fields_q;
        @subtotal_fields_q{ @$subtotal_fields_q } = (1) x @$subtotal_fields_q;
        map { ($r->perror("'Summary totals on' fields must be a subset of the 'Report on' fields [$_ is not], please try again"),return undef)
            unless exists($select_fields_q{$_}) || $_ eq $DBIx::Knowledge::REPORT_TOTAL_KEY } @$subtotal_fields_q;
        map { push(@subtotal_fields,$_->{ID}) if $subtotal_fields_q{$_->{ID}} } @subtotal_field_picklist;
##at on freeform field, please mind the LEGEND key 'GRANDTOT'
    }

##at by default, results will be sorted in the same order as the order of the select list
    my $orderby_q = $q->{FORMAT}{ORDER_FIELD};
    my @order_fields = ();
    if (length($orderby_q))
    {
        foreach my $order_term (split(/\s*,\s*/,$orderby_q))
        {
            my $desc_needed = $order_term =~ s/\s+desc//i;
            $order_term = uc($order_term);
##at should be done in validate_query()
            ($r->perror("Invalid term entered in 'Order by' sequence [$order_term], please go back and correct"),return undef)
                unless exists($potential_select_fields_by_order_legend{$order_term});
            my $term_id = $potential_select_fields_by_order_legend{$order_term};
            ($r->perror("'Ordered by' fields must be a subset of the 'Report on' fields [$order_term is not], please try again"),return undef)
                unless exists($select_fields_q{$term_id->{ID}});
            my %order_field = ( %{$term_id} );
            $order_field{descending_order} = $desc_needed;
            push(@order_fields,\%order_field) unless length($r->{np_uname}) && !$term_id->{np_allow};
        }
    }

    my $result_limit = $q->{FORMAT}{RESULT_LIMIT};

    my $where_sql = '';
    if ($q->{CRITERIA})
    {
        my $crit = $q->{CRITERIA};
        my @keys = keys(%$crit);
        foreach my $key (@keys)
        {
            delete($crit->{$key}) if length($r->{np_uname}) && !$np_allowed_crit_fields{$key};
        }
        $crit->{NP_USERNAME} = $r->{np_uname} if length($r->{np_uname});
        my $where = new CGI::CRUD::TableIO($r->dbh());
        $where_sql = $where->where_sql($crit,$self->{report_table_name});
        defined($where_sql) or ($r->server_error(),return undef);
    }

    my %want_subtotal_fields = ();

    my $sql = $self->{sql_gen}->sql_rollup($self->{report_table_name},\@select_fields,$where_sql,\@subtotal_fields,\@order_fields,\%want_subtotal_fields) or
        ($r->perror("Report definition is invalid:<br>$self->{sql_gen}->{errstr}<br>Please go back and try again"),return undef);

    my $dbh = $r->dbh();
    my $report_sth = $dbh->prepare($sql) or ($r->perror("Report definition is invalid, please go back and try again"),return undef);
    $report_sth->execute() or ($r->server_error(),return undef);

    my $title = CGI::AutoForm->escape($r->{tpl_vars}{REPORT_TITLE});
    my $tdate = CGI::AutoForm->escape($r->{tpl_vars}{REPORT_DATE});
    
    my $html = <<HTML;
<H3 style="padding-bottom:0px;margin-bottom:0px;margin-top: 10px;">$title</H3>
<p style="padding-top:0px;margin-top:0px;">Report created $tdate</p>
HTML

    my $aux_info_fields = $q->{FORMAT}{AUX_INFO_FIELD};
    $aux_info_fields = [ $aux_info_fields ] unless ref $aux_info_fields;

    mark_aux_fields(\@select_fields, $aux_info_fields);

    my $out = new DBIx::Knowledge::Output($q->{FORMAT}{HEADER_REPEAT});
#OPDebugUtil::ddump(\@select_fields);
    my $outp = $out->generate_html($report_sth,\@select_fields,\%want_subtotal_fields) or ($r->server_error(),return undef);

    $html .= $$outp;
    
    $html .= <<HTML;
<p style="margin: 30px;">
<a href="$r->{action}?$r->{tpl_vars}{FULL_SAVED_QUERY}">Save report URL for later</a><br>
<a href="$r->{action}?$r->{tpl_vars}{CUSTOM_SAVED_QUERY}">Further customize this report</a>
</p>
HTML
    



( run in 1.143 second using v1.01-cache-2.11-cpan-9581c071862 )