summaryrefslogtreecommitdiff
path: root/FS/FS/report_batch.pm
diff options
context:
space:
mode:
authorMark Wells <mark@freeside.biz>2015-09-18 10:18:43 -0700
committerMark Wells <mark@freeside.biz>2015-09-18 10:19:08 -0700
commit77a4ccff7384368888fe88379f01f9ddc34244d8 (patch)
treedf949604ac1bbd5bb17b647fa37b7c0fb42dee1d /FS/FS/report_batch.pm
parent8cbf995cfbc851821242f4ab5e03713b3de8ef69 (diff)
send commission reports by email, #33101
Diffstat (limited to 'FS/FS/report_batch.pm')
-rw-r--r--FS/FS/report_batch.pm321
1 files changed, 321 insertions, 0 deletions
diff --git a/FS/FS/report_batch.pm b/FS/FS/report_batch.pm
new file mode 100644
index 0000000..64412df
--- /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<FS::msg_template> 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<not> 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<style>\n$stylesheet\n</style>\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<FS::Record>
+
+=cut
+
+1;
+