X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Freport_batch.pm;fp=FS%2FFS%2Freport_batch.pm;h=64412dfba034bd93b09f8feea539e50468c61d62;hp=0000000000000000000000000000000000000000;hb=c0c5709fb022b83a482d0b35f7094505766d5868;hpb=7bce756e86a4307d6cad49a690f22a321acc9981 diff --git a/FS/FS/report_batch.pm b/FS/FS/report_batch.pm new file mode 100644 index 000000000..64412dfba --- /dev/null +++ b/FS/FS/report_batch.pm @@ -0,0 +1,321 @@ +package FS::report_batch; +use base qw( FS::Record ); + +use strict; +use FS::Record qw( qsearch qsearchs dbdef ); +use FS::msg_template; +use FS::cust_main; +use FS::Misc::DateTime qw(parse_datetime); +use FS::Mason qw(mason_interps); +use URI::Escape; +use HTML::Defang; + +our $DEBUG = 0; + +=head1 NAME + +FS::report_batch - Object methods for report_batch records + +=head1 SYNOPSIS + + use FS::report_batch; + + $record = new FS::report_batch \%hash; + $record = new FS::report_batch { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::report_batch object represents an order to send a batch of reports to +their respective customers or other contacts. FS::report_batch inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item reportbatchnum + +primary key + +=item reportname + +The name of the report, which will be the same as the file name (minus any +directory names). There's an enumerated set of these; you can't use just any +report. + +=item send_date + +The date the report was sent. + +=item agentnum + +The agentnum to limit the report to, if any. + +=item sdate + +The start date of the report period. + +=item edate + +The end date of the report period. + +=item usernum + +The user who ordered the report. + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new report batch. To add the record to the database, see L<"insert">. + +=cut + +sub table { 'report_batch'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=item delete + +Deletes this record from the database. + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=item check + +Checks all fields to make sure this is a valid record. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('reportbatchnum') + || $self->ut_text('reportname') + || $self->ut_numbern('agentnum') + || $self->ut_numbern('sdate') + || $self->ut_numbern('edate') + || $self->ut_numbern('usernum') + ; + return $error if $error; + + $self->set('send_date', time); + + $self->SUPER::check; +} + +=back + +=head1 SUBROUTINES + +=over 4 + +=item process_send_report JOB, PARAMS + +Takes a hash of PARAMS, determines all contacts who need to receive a report, +and sends it to them. On completion, creates and stores a report_batch record. +JOB is a queue job to receive status messages. + +PARAMS can include: + +- reportname: the name of the report (listed in the C<%sendable_reports> hash). +Required. +- msgnum: the L to use for this report. Currently the +content of the template is ignored, but the subject line and From/Bcc addresses +are still used. Required. +- agentnum: the agent to limit the report to. +- beginning, ending: the date range to run the report, as human-readable +dates (I unix timestamps). + +=cut + +# trying to keep this data-driven, with parameters that tell how the report is +# to be handled rather than callbacks. +# - path: where under the document root the report is located +# - domain: which table to query for objects on which the report is run. +# Each record in that table produces one report. +# - cust_main: the method on that object that returns its linked customer (to +# which the report will be sent). If the table has a 'custnum' field, this +# can be omitted. +our %sendable_reports = ( + 'sales_commission_pkg' => { + 'name' => 'Sales commission per package', + 'path' => '/search/sales_commission_pkg.html', + 'domain' => 'sales', + 'cust_main' => 'sales_cust_main', + }, +); + +sub process_send_report { + my $job = shift; + my $param = shift; + + my $msgnum = $param->{'msgnum'}; + my $template = FS::msg_template->by_key($msgnum) + or die "msg_template $msgnum not found\n"; + + my $reportname = $param->{'reportname'}; + my $info = $sendable_reports{$reportname} + or die "don't know how to send report '$reportname'\n"; + + # the most important thing: which report is it? + my $path = $info->{'path'}; + + # find all targets for the report: + # - those matching the agentnum if there is one. + # - those that aren't disabled. + my $domain = $info->{domain}; + my $dbt = dbdef->table($domain); + my $hashref = {}; + if ( $param->{'agentnum'} and $dbt->column('agentnum') ) { + $hashref->{'agentnum'} = $param->{'agentnum'}; + } + if ( $dbt->column('disabled') ) { + $hashref->{'disabled'} = ''; + } + my @records = qsearch($domain, $hashref); + my $num_targets = scalar(@records); + return if $num_targets == 0; + my $sent = 0; + + my $outbuf; + my ($fs_interp) = mason_interps('standalone', 'outbuf' => \$outbuf); + # if generating the report fails, we want to capture the error and exit, + # not send it. + $fs_interp->error_mode('fatal'); + $fs_interp->error_format('brief'); + + # we have to at least have an RT::Handle + require RT; + RT::LoadConfig(); + RT::Init(); + + # hold onto all the reports until we're sure they generated correctly. + my %cust_main; + my %report_content; + + # grab the stylesheet + ### note: if we need the ability to support different stylesheets, this + ### is the place to put it in + eval { $fs_interp->exec('/elements/freeside.css') }; + die "couldn't load stylesheet via Mason: $@\n" if $@; + my $stylesheet = $outbuf; + + my $pkey = $dbt->primary_key; + foreach my $rec (@records) { + + $job->update_statustext(int( 100 * $sent / $num_targets )); + my $pkey_val = $rec->get($pkey); # e.g. sales.salesnum + + # find the customer we're sending to, and their email + my $cust_main; + if ( $info->{'cust_main'} ) { + my $cust_method = $info->{'cust_main'}; + $cust_main = $rec->$cust_method; + } elsif ( $rec->custnum ) { + $cust_main = FS::cust_main->by_key($rec->custnum); + } else { + warn "$pkey = $pkey_val has no custnum; not sending report\n"; + next; + } + my @email = $cust_main->invoicing_list_emailonly; + if (!@email) { + warn "$pkey = $pkey_val has no email destinations\n" if $DEBUG; + next; + } + + # params to send to the report (as if from the user's browser) + my @report_param = ( # maybe list these in $info + agentnum => $param->{'agentnum'}, + beginning => $param->{'beginning'}, + ending => $param->{'ending'}, + $pkey => $pkey_val, + _type => 'html-print', + ); + + # build a query string + my $query_string = ''; + while (@report_param) { + $query_string .= uri_escape(shift @report_param) + . '=' + . uri_escape(shift @report_param); + $query_string .= ';' if @report_param; + } + warn "$path?$query_string\n\n" if $DEBUG; + + # run the report! + $FS::Mason::Request::QUERY_STRING = $query_string; + $FS::Mason::Request::FSURL = ''; + $outbuf = ''; + eval { $fs_interp->exec($path) }; + die "creating report for $pkey = $pkey_val: $@" if $@; + + # make some adjustments to the report + my $html_defang; + $html_defang = HTML::Defang->new( + url_callback => sub { 1 }, # strip all URLs (they're not accessible) + tags_to_callback => [ 'body' ], # and after the BODY tag... + tags_callback => sub { + my $isEndTag = $_[4]; + $html_defang->add_to_output("\n\n") + unless $isEndTag; + }, + ); + $outbuf = $html_defang->defang($outbuf); + + $cust_main{ $cust_main->custnum } = $cust_main; + $report_content{ $cust_main->custnum } = $outbuf; + } # foreach $rec + + $job->update_statustext('Sending reports...'); + foreach my $custnum (keys %cust_main) { + # create an email message with the report as body + # change this when backporting to 3.x + $template->send( + cust_main => $cust_main{$custnum}, + object => $cust_main{$custnum}, + msgtype => 'report', + override_content => $report_content{$custnum}, + ); + } + + my $self = FS::report_batch->new({ + reportname => $param->{'reportname'}, + agentnum => $param->{'agentnum'}, + sdate => parse_datetime($param->{'beginning'}), + edate => parse_datetime($param->{'ending'}), + usernum => $job->usernum, + msgnum => $param->{'msgnum'}, + }); + my $error = $self->insert; + warn "error recording completion of report: $error\n" if $error; + +} + +=head1 SEE ALSO + +L + +=cut + +1; +