From: Mark Wells Date: Wed, 7 Sep 2016 22:08:14 +0000 (-0700) Subject: add email delivery of saved searches, #72101 X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=2d2662d900652d9b1f242e9affd5a6e67c453f4d add email delivery of saved searches, #72101 --- diff --git a/FS/FS/Cron/send_subscribed.pm b/FS/FS/Cron/send_subscribed.pm new file mode 100644 index 000000000..2b1f662e6 --- /dev/null +++ b/FS/FS/Cron/send_subscribed.pm @@ -0,0 +1,32 @@ +package FS::Cron::send_subscribed; + +use strict; +use base 'Exporter'; +use FS::saved_search; +use FS::Record qw(qsearch); +use FS::queue; + +our @EXPORT_OK = qw( send_subscribed ); +our $DEBUG = 1; + +sub send_subscribed { + + my @subs = qsearch('saved_search', { + 'disabled' => '', + 'freq' => { op => '!=', value => '' }, + }); + foreach my $saved_search (@subs) { + my $date = $saved_search->next_send_date; + warn "checking '".$saved_search->searchname."' with date $date\n" + if $DEBUG; + + if ( $^T > $saved_search->next_send_date ) { + warn "queueing delivery\n"; + my $job = FS::queue->new({ job => 'FS::saved_search::queueable_send' }); + $job->insert( $saved_search->searchnum ); + } + } + +} + +1; diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index e8a1af6c6..ee87b2de4 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -416,7 +416,6 @@ if ( -e $addl_handler_use_file ) { use FS::commission_schedule; use FS::commission_rate; use FS::saved_search; - use FS::saved_search_option; # Sammath Naur if ( $FS::Mason::addl_handler_use ) { diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 4ff9db211..df987ffc7 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -7492,6 +7492,7 @@ sub tables_hashref { 'usernum', 'int', 'NULL', '', '', '', 'searchname', 'varchar', '', $char_d, '', '', 'path', 'varchar', '', $char_d, '', '', + 'params', 'text', 'NULL', '', '', '', 'disabled', 'char', 'NULL', 1, '', '', 'freq', 'varchar', 'NULL', 16, '', '', 'last_sent', 'int', 'NULL', '', '', '', @@ -7507,23 +7508,6 @@ sub tables_hashref { ], }, - 'saved_search_option' => { - 'columns' => [ - 'optionnum', 'serial', '', '', '', '', - 'searchnum', 'int', '', '', '', '', - 'optionname', 'varchar', '', $char_d, '', '', - 'optionvalue', 'text', 'NULL', '', '', '', - ], - 'primary_key' => 'optionnum', - 'unique' => [ [ 'searchnum', 'optionname' ] ], - 'index' => [], - 'foreign_keys' => [ - { columns => [ 'searchnum' ], - table => 'saved_search', - }, - ], - }, - # name type nullability length default local #'new_table' => { diff --git a/FS/FS/log_context.pm b/FS/FS/log_context.pm index 37befb515..ee3e413ee 100644 --- a/FS/FS/log_context.pm +++ b/FS/FS/log_context.pm @@ -13,6 +13,8 @@ my @contexts = ( qw( FS::pay_batch::import_from_gateway FS::part_pkg FS::Misc::Geo::standardize_uscensus + FS::saved_search::send + FS::saved_search::render Cron::bill Cron::backup Cron::upload diff --git a/FS/FS/saved_search.pm b/FS/FS/saved_search.pm index 075d759f6..252dc71e5 100644 --- a/FS/FS/saved_search.pm +++ b/FS/FS/saved_search.pm @@ -1,13 +1,15 @@ package FS::saved_search; -use base qw( FS::option_Common FS::Record ); +use base qw( FS::Record ); use strict; use FS::Record qw( qsearch qsearchs ); use FS::Conf; +use FS::Log; +use FS::Misc qw(send_email); +use MIME::Entity; use Class::Load 'load_class'; use URI::Escape; use DateTime; -use Try::Tiny; =head1 NAME @@ -56,6 +58,10 @@ A descriptive name. The path to the page within the Mason document space. +=item params + +The query string for the search. + =item disabled 'Y' to hide the search from the user's Reports / Saved menu. @@ -128,6 +134,7 @@ sub check { #|| $self->ut_foreign_keyn('usernum', 'access_user', 'usernum') || $self->ut_text('searchname') || $self->ut_text('path') + || $self->ut_textn('params') # URL-escaped, so ut_textn || $self->ut_flag('disabled') || $self->ut_enum('freq', [ '', 'daily', 'weekly', 'monthly' ]) || $self->ut_numbern('last_sent') @@ -138,6 +145,14 @@ sub check { $self->SUPER::check; } +sub replace_check { + my ($new, $old) = @_; + if ($new->usernum != $old->usernum) { + return "can't change owner of a saved search"; + } + ''; +} + =item next_send_date Returns the next date this report should be sent next. If it's not set for @@ -168,8 +183,6 @@ Returns the CGI query string for the parameters to this report. =cut -# multivalued options are newline-separated in the database - sub query_string { my $self = shift; @@ -177,12 +190,7 @@ sub query_string { $type = 'html-print' if $type eq '' || $type eq 'html'; $type = '.xls' if $type eq 'xls'; my $query = "_type=$type"; - my %options = $self->options; - foreach my $k (keys %options) { - foreach my $v (split("\n", $options{$k})) { - $query .= ';' . uri_escape($k) . '=' . uri_escape($v); - } - } + $query .= ';' . $self->params if $self->params; $query; } @@ -194,6 +202,7 @@ Returns the report content as an HTML or Excel file. sub render { my $self = shift; + my $log = FS::Log->new('FS::saved_search::render'); my $outbuf; # delayed loading @@ -214,7 +223,7 @@ sub render { # local $ENV{SERVER_NAME} = 'localhost'; #? # local $ENV{SCRIPT_NAME} = '/freeside'. $self->path; - my $mason_request = $fs_interp->make_request(comp => $self->path); + my $mason_request = $fs_interp->make_request(comp => '/' . $self->path); local $@; eval { $mason_request->exec(); }; @@ -224,9 +233,9 @@ sub render { $error = $error->message; } - warn "Error rendering " . $self->path . + $log->error("Error rendering " . $self->path . " for " . $self->access_user->username . - ":\n$error\n"; + ":\n$error\n"); # send it to the user anyway, so there's a way to diagnose the error $outbuf = '

Error

There was an error generating the report "'.$self->searchname.'".

@@ -237,6 +246,63 @@ sub render { return $outbuf; } +=item send + +Sends the search by email. If anything fails, logs and returns an error. + +=cut + +sub send { + my $self = shift; + my $log = FS::Log->new('FS::saved_search::send'); + my $conf = FS::Conf->new; + my $user = $self->access_user; + my $username = $user->username; + my $user_email = $user->option('email_address'); + my $error; + if (!$user_email) { + $error = "User '$username' has no email address."; + $log->error($error); + return $error; + } + $log->debug('Rendering saved search'); + my $content = $self->render; + # XXX come back to this for content-type options + my $part = MIME::Entity->build( + 'Type' => 'text/html', + 'Encoding' => 'quoted-printable', # change this for spreadsheet + 'Disposition' => 'inline', + 'Data' => $content, + ); + + my %email_param = ( + 'from' => $conf->config('invoice_from'), + 'to' => $user_email, + 'subject' => $self->searchname, + 'nobody' => 1, + 'mimeparts' => [ $part ], + ); + + $log->debug('Sending to '.$user_email); + $error = send_email(%email_param); + + # update the timestamp + $self->set('last_sent', time); + $error ||= $self->replace; + if ($error) { + $log->error($error); + return $error; + } + +} + +sub queueable_send { + my $searchnum = shift; + my $self = FS::saved_search->by_key($searchnum) + or die "searchnum $searchnum not found\n"; + $self->send; +} + =back =head1 SEE ALSO diff --git a/FS/FS/saved_search_option.pm b/FS/FS/saved_search_option.pm deleted file mode 100644 index f349af393..000000000 --- a/FS/FS/saved_search_option.pm +++ /dev/null @@ -1,124 +0,0 @@ -package FS::saved_search_option; -use base qw( FS::Record ); - -use strict; -use FS::Record qw( qsearch qsearchs ); - -=head1 NAME - -FS::saved_search_option - Object methods for saved_search_option records - -=head1 SYNOPSIS - - use FS::saved_search_option; - - $record = new FS::saved_search_option \%hash; - $record = new FS::saved_search_option { 'column' => 'value' }; - - $error = $record->insert; - - $error = $new_record->replace($old_record); - - $error = $record->delete; - - $error = $record->check; - -=head1 DESCRIPTION - -An FS::saved_search_option object represents a CGI parameter for a report -saved in L. FS::saved_search_option inherits from -FS::Record. The following fields are currently supported: - -=over 4 - -=item optionnum - -primary key - -=item searchnum - -searchnum - -=item optionname - -optionname - -=item optionvalue - -optionvalue - - -=back - -=head1 METHODS - -=over 4 - -=item new HASHREF - -Creates a new parameter. To add the record to the database, see -L<"insert">. - -Note that this stores the hash reference, not a distinct copy of the hash it -points to. You can ask the object for a copy with the I method. - -=cut - -# the new method can be inherited from FS::Record, if a table method is defined - -sub table { 'saved_search_option'; } - -=item insert - -Adds this record to the database. If there is an error, returns the error, -otherwise returns false. - -=item delete - -Delete 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 example. If there is -an error, returns the error, otherwise returns false. Called by the insert -and replace methods. - -=cut - -# the check method should currently be supplied - FS::Record contains some -# data checking routines - -sub check { - my $self = shift; - -# unpack these from the format used by CGI - my $optionvalue = $self->optionvalue; - $optionvalue =~ s/\0/\n/g; - - my $error = - $self->ut_numbern('optionnum') - || $self->ut_number('searchnum') -# || $self->ut_foreign_key('searchnum', 'saved_search', 'searchnum') - || $self->ut_text('optionname') - || $self->ut_textn('optionvalue') - ; - return $error if $error; - - $self->SUPER::check; -} - -=back - -=head1 SEE ALSO - -L - -=cut - -1; - diff --git a/FS/MANIFEST b/FS/MANIFEST index d06f2637f..73a740f63 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -876,5 +876,3 @@ FS/commission_rate.pm t/commission_rate.t FS/saved_search.pm t/saved_search.t -FS/saved_search_option.pm -t/saved_search_option.t diff --git a/FS/bin/freeside-daily b/FS/bin/freeside-daily index 6a2daf934..03d235061 100755 --- a/FS/bin/freeside-daily +++ b/FS/bin/freeside-daily @@ -79,6 +79,10 @@ pay_batch_receive(%opt); use FS::Cron::export_batch qw(export_batch_submit); export_batch_submit(%opt); +#does nothing unless there are users with subscribed searches +use FS::Cron::send_subscribed qw(send_subscribed); +send_subscribed(%opt); + #clears out cacti imports & deletes select database cache files use FS::Cron::cleanup qw( cleanup cleanup_before_backup ); cleanup_before_backup(); diff --git a/FS/t/saved_search_option.t b/FS/t/saved_search_option.t deleted file mode 100644 index f30bfb806..000000000 --- a/FS/t/saved_search_option.t +++ /dev/null @@ -1,5 +0,0 @@ -BEGIN { $| = 1; print "1..1\n" } -END {print "not ok 1\n" unless $loaded;} -use FS::saved_search_option; -$loaded=1; -print "ok 1\n";