1 package FS::report_batch;
2 use base qw( FS::Record );
5 use FS::Record qw( qsearch qsearchs dbdef );
8 use FS::Misc::DateTime qw(parse_datetime);
9 use FS::Mason qw(mason_interps);
17 FS::report_batch - Object methods for report_batch records
23 $record = new FS::report_batch \%hash;
24 $record = new FS::report_batch { 'column' => 'value' };
26 $error = $record->insert;
28 $error = $new_record->replace($old_record);
30 $error = $record->delete;
32 $error = $record->check;
36 An FS::report_batch object represents an order to send a batch of reports to
37 their respective customers or other contacts. FS::report_batch inherits from
38 FS::Record. The following fields are currently supported:
48 The name of the report, which will be the same as the file name (minus any
49 directory names). There's an enumerated set of these; you can't use just any
54 The date the report was sent.
58 The agentnum to limit the report to, if any.
62 The start date of the report period.
66 The end date of the report period.
70 The user who ordered the report.
80 Creates a new report batch. To add the record to the database, see L<"insert">.
84 sub table { 'report_batch'; }
88 Adds this record to the database. If there is an error, returns the error,
89 otherwise returns false.
93 Deletes this record from the database.
95 =item replace OLD_RECORD
97 Replaces the OLD_RECORD with this one in the database. If there is an error,
98 returns the error, otherwise returns false.
102 Checks all fields to make sure this is a valid record. If there is
103 an error, returns the error, otherwise returns false. Called by the insert
112 $self->ut_numbern('reportbatchnum')
113 || $self->ut_text('reportname')
114 || $self->ut_numbern('agentnum')
115 || $self->ut_numbern('sdate')
116 || $self->ut_numbern('edate')
117 || $self->ut_numbern('usernum')
119 return $error if $error;
121 $self->set('send_date', time);
132 =item process_send_report JOB, PARAMS
134 Takes a hash of PARAMS, determines all contacts who need to receive a report,
135 and sends it to them. On completion, creates and stores a report_batch record.
136 JOB is a queue job to receive status messages.
140 - reportname: the name of the report (listed in the C<%sendable_reports> hash).
142 - msgnum: the L<FS::msg_template> to use for this report. Currently the
143 content of the template is ignored, but the subject line and From/Bcc addresses
144 are still used. Required.
145 - agentnum: the agent to limit the report to.
146 - beginning, ending: the date range to run the report, as human-readable
147 dates (I<not> unix timestamps).
151 # trying to keep this data-driven, with parameters that tell how the report is
152 # to be handled rather than callbacks.
153 # - path: where under the document root the report is located
154 # - domain: which table to query for objects on which the report is run.
155 # Each record in that table produces one report.
156 # - cust_main: the method on that object that returns its linked customer (to
157 # which the report will be sent). If the table has a 'custnum' field, this
159 our %sendable_reports = (
160 'sales_commission_pkg' => {
161 'name' => 'Sales commission per package',
162 'path' => '/search/sales_commission_pkg.html',
164 'cust_main' => 'sales_cust_main',
168 sub process_send_report {
172 my $msgnum = $param->{'msgnum'};
173 my $template = FS::msg_template->by_key($msgnum)
174 or die "msg_template $msgnum not found\n";
176 my $reportname = $param->{'reportname'};
177 my $info = $sendable_reports{$reportname}
178 or die "don't know how to send report '$reportname'\n";
180 # the most important thing: which report is it?
181 my $path = $info->{'path'};
183 # find all targets for the report:
184 # - those matching the agentnum if there is one.
185 # - those that aren't disabled.
186 my $domain = $info->{domain};
187 my $dbt = dbdef->table($domain);
189 if ( $param->{'agentnum'} and $dbt->column('agentnum') ) {
190 $hashref->{'agentnum'} = $param->{'agentnum'};
192 if ( $dbt->column('disabled') ) {
193 $hashref->{'disabled'} = '';
195 my @records = qsearch($domain, $hashref);
196 my $num_targets = scalar(@records);
197 return if $num_targets == 0;
201 my ($fs_interp) = mason_interps('standalone', 'outbuf' => \$outbuf);
202 # if generating the report fails, we want to capture the error and exit,
204 $fs_interp->error_mode('fatal');
205 $fs_interp->error_format('brief');
207 # we have to at least have an RT::Handle
212 # hold onto all the reports until we're sure they generated correctly.
216 # grab the stylesheet
217 ### note: if we need the ability to support different stylesheets, this
218 ### is the place to put it in
219 eval { $fs_interp->exec('/elements/freeside.css') };
220 die "couldn't load stylesheet via Mason: $@\n" if $@;
221 my $stylesheet = $outbuf;
223 my $pkey = $dbt->primary_key;
224 foreach my $rec (@records) {
226 $job->update_statustext(int( 100 * $sent / $num_targets ));
227 my $pkey_val = $rec->get($pkey); # e.g. sales.salesnum
229 # find the customer we're sending to, and their email
231 if ( $info->{'cust_main'} ) {
232 my $cust_method = $info->{'cust_main'};
233 $cust_main = $rec->$cust_method;
234 } elsif ( $rec->custnum ) {
235 $cust_main = FS::cust_main->by_key($rec->custnum);
237 warn "$pkey = $pkey_val has no custnum; not sending report\n";
240 my @email = $cust_main->invoicing_list_emailonly;
242 warn "$pkey = $pkey_val has no email destinations\n" if $DEBUG;
246 # params to send to the report (as if from the user's browser)
247 my @report_param = ( # maybe list these in $info
248 agentnum => $param->{'agentnum'},
249 beginning => $param->{'beginning'},
250 ending => $param->{'ending'},
252 _type => 'html-print',
255 # build a query string
256 my $query_string = '';
257 while (@report_param) {
258 $query_string .= uri_escape(shift @report_param)
260 . uri_escape(shift @report_param);
261 $query_string .= ';' if @report_param;
263 warn "$path?$query_string\n\n" if $DEBUG;
266 $FS::Mason::Request::QUERY_STRING = $query_string;
267 $FS::Mason::Request::FSURL = '';
269 eval { $fs_interp->exec($path) };
270 die "creating report for $pkey = $pkey_val: $@" if $@;
272 # make some adjustments to the report
274 $html_defang = HTML::Defang->new(
275 url_callback => sub { 1 }, # strip all URLs (they're not accessible)
276 tags_to_callback => [ 'body' ], # and after the BODY tag...
277 tags_callback => sub {
278 my $isEndTag = $_[4];
279 $html_defang->add_to_output("\n<style>\n$stylesheet\n</style>\n")
283 $outbuf = $html_defang->defang($outbuf);
285 $cust_main{ $cust_main->custnum } = $cust_main;
286 $report_content{ $cust_main->custnum } = $outbuf;
289 $job->update_statustext('Sending reports...');
290 foreach my $custnum (keys %cust_main) {
291 # create an email message with the report as body
292 # change this when backporting to 3.x
294 cust_main => $cust_main{$custnum},
295 object => $cust_main{$custnum},
297 override_content => $report_content{$custnum},
301 my $self = FS::report_batch->new({
302 reportname => $param->{'reportname'},
303 agentnum => $param->{'agentnum'},
304 sdate => parse_datetime($param->{'beginning'}),
305 edate => parse_datetime($param->{'ending'}),
306 usernum => $job->usernum,
307 msgnum => $param->{'msgnum'},
309 my $error = $self->insert;
310 warn "error recording completion of report: $error\n" if $error;