diff options
author | mark <mark> | 2010-09-17 18:12:08 +0000 |
---|---|---|
committer | mark <mark> | 2010-09-17 18:12:08 +0000 |
commit | 6c9cd1c36adbb9fc950fcf0a0b269fa6f16838a1 (patch) | |
tree | 1c90e0377b37a98bd7a1757532648a9e966f291a /FS | |
parent | 853fca259ec006d4a5f3ce046e5334210412baac (diff) |
email_search_result for cust_pkg and svc_broadband, RT#8736
Diffstat (limited to 'FS')
-rw-r--r-- | FS/FS/Mason.pm | 1 | ||||
-rw-r--r-- | FS/FS/cust_main.pm | 162 | ||||
-rw-r--r-- | FS/FS/cust_main_Mixin.pm | 196 | ||||
-rwxr-xr-x | FS/FS/svc_broadband.pm | 120 |
4 files changed, 323 insertions, 156 deletions
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index 7be78aa03..d769d8514 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -113,6 +113,7 @@ if ( -e $addl_handler_use_file ) { use Locale::Country; use Business::US::USPS::WebTools::AddressStandardization; use LWP::UserAgent; + use Storable qw( nfreeze thaw ); use FS; use FS::UID qw( getotaker dbh datasrc driver_name ); use FS::Record qw( qsearch qsearchs fields dbdef diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index f4b9c5993..007beec92 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -2,7 +2,11 @@ package FS::cust_main; require 5.006; use strict; -use base qw( FS::otaker_Mixin FS::payinfo_Mixin FS::Record ); +use base qw( FS::otaker_Mixin + FS::payinfo_Mixin + FS::cust_main_Mixin + FS::Record + ); use vars qw( @EXPORT_OK $DEBUG $me $conf @encrypted_fields $import $ignore_expired_card @@ -8048,7 +8052,7 @@ sub search { ? @{ $params->{'payby'} } : ( $params->{'payby'} ); - @payby = grep /^([A-Z]{4})$/, @{ $params->{'payby'} }; + @payby = grep /^([A-Z]{4})$/, @payby; push @where, '( '. join(' OR ', map "cust_main.payby = '$_'", @payby). ' )' if @payby; @@ -8183,160 +8187,6 @@ sub search { } -=item email_search_result HASHREF - -(Class method) - -Emails a notice to the specified customers. - -Valid parameters are those of the L<search> method, plus the following: - -=over 4 - -=item from - -From: address - -=item subject - -Email Subject: - -=item html_body - -HTML body - -=item text_body - -Text body - -=item job - -Optional job queue job for status updates. - -=back - -Returns an error message, or false for success. - -If an error occurs during any email, stops the enture send and returns that -error. Presumably if you're getting SMTP errors aborting is better than -retrying everything. - -=cut - -sub email_search_result { - my($class, $params) = @_; - - my $from = delete $params->{from}; - my $subject = delete $params->{subject}; - my $html_body = delete $params->{html_body}; - my $text_body = delete $params->{text_body}; - my $error = ''; - - my $job = delete $params->{'job'} - or die "email_search_result must run from the job queue.\n"; - - $params->{'payby'} = [ split(/\0/, $params->{'payby'}) ] - unless ref($params->{'payby'}); - - my $sql_query = $class->search($params); - - my $count_query = delete($sql_query->{'count_query'}); - my $count_sth = dbh->prepare($count_query) - or die "Error preparing $count_query: ". dbh->errstr; - $count_sth->execute - or die "Error executing $count_query: ". $count_sth->errstr; - my $count_arrayref = $count_sth->fetchrow_arrayref; - my $num_cust = $count_arrayref->[0]; - - #my @extra_headers = @{ delete($sql_query->{'extra_headers'}) }; - #my @extra_fields = @{ delete($sql_query->{'extra_fields'}) }; - - - my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo - my @retry_jobs = (); - my $success = 0; - - #eventually order+limit magic to reduce memory use? - foreach my $cust_main ( qsearch($sql_query) ) { - - #progressbar first, so that the count is right - $num++; - if ( time - $min_sec > $last ) { - my $error = $job->update_statustext( - int( 100 * $num / $num_cust ) - ); - die $error if $error; - $last = time; - } - - my $to = $cust_main->invoicing_list_emailonly_scalar; - - if( $to ) { - my @message = ( - 'from' => $from, - 'to' => $to, - 'subject' => $subject, - 'html_body' => $html_body, - 'text_body' => $text_body, - ); - - $error = send_email( generate_email( @message ) ); - - if($error) { - # queue the sending of this message so that the user can see what we - # tried to do, and retry if desired - my $queue = new FS::queue { - 'job' => 'FS::Misc::process_send_email', - 'custnum' => $cust_main->custnum, - 'status' => 'failed', - 'statustext' => $error, - }; - $queue->insert(@message); - push @retry_jobs, $queue; - } - else { - $success++; - } - } - - if($success == 0 and - (scalar(@retry_jobs) > 10 or $num == $num_cust) - ) { - # 10 is arbitrary, but if we have enough failures, that's - # probably a configuration or network problem, and we - # abort the batch and run away screaming. - # We NEVER do this if anything was successfully sent. - $_->delete foreach (@retry_jobs); - return "multiple failures: '$error'\n"; - } - } - - if(@retry_jobs) { - # fail the job, but with a status message that makes it clear - # something was sent. - return "Sent $success, failed ".scalar(@retry_jobs).". Failed attempts placed in job queue.\n"; - } - - return ''; -} - -sub process_email_search_result { - my $job = shift; - #warn "$me process_re_X $method for job $job\n" if $DEBUG; - - my $param = thaw(decode_base64(shift)); - warn Dumper($param) if $DEBUG; - - $param->{'job'} = $job; - - $param->{'payby'} = [ split(/\0/, $param->{'payby'}) ] - unless ref($param->{'payby'}); - - my $error = FS::cust_main->email_search_result( $param ); - die $error if $error; - -} - =item fuzzy_search FUZZY_HASHREF [ HASHREF, SELECT, EXTRA_SQL, CACHE_OBJ ] Performs a fuzzy (approximate) search and returns the matching FS::cust_main diff --git a/FS/FS/cust_main_Mixin.pm b/FS/FS/cust_main_Mixin.pm index 3dde95f2e..b446d6965 100644 --- a/FS/FS/cust_main_Mixin.pm +++ b/FS/FS/cust_main_Mixin.pm @@ -5,6 +5,8 @@ use vars qw( $DEBUG $me ); use Carp qw( confess ); use FS::UID qw(dbh); use FS::cust_main; +use FS::Record qw( qsearch qsearchs ); +use FS::Misc qw( send_email generate_email ); $DEBUG = 0; $me = '[FS::cust_main_Mixin]'; @@ -33,6 +35,11 @@ for example, from a JOINed search. See httemplate/search/ for examples. sub cust_unlinked_msg { '(unlinked)'; } sub cust_linked { $_[0]->custnum; } +sub cust_main { + my $self = shift; + $self->cust_linked ? qsearchs('cust_main', {custnum => $self->custnum}) : ''; +} + =item display_custnum Given an object that contains fields from cust_main (say, from a JOINed @@ -330,6 +337,195 @@ sub cust_search_sql { } +=item email_search_result HASHREF + +Emails a notice to the specified customers. Customers without +invoice email destinations will be skipped. + +Parameters: + +=over 4 + +=item job + +Queue job for status updates. Required. + +=item search + +Hashref of params to the L<search()> method. Required. + +=item msgnum + +Message template number (see L<FS::msg_template>). Overrides all +of the following options. + +=item from + +From: address + +=item subject + +Email Subject: + +=item html_body + +HTML body + +=item text_body + +Text body + +=back + +Returns an error message, or false for success. + +If any messages fail to send, they will be queued as individual +jobs which can be manually retried. If the first ten messages +in the job fail, the entire job will abort and return an error. + +=cut + +use Storable qw(thaw); +use MIME::Base64; +use Data::Dumper qw(Dumper); + +sub email_search_result { + my($class, $param) = @_; + + my $msgnum = $param->{msgnum}; + my $from = delete $param->{from}; + my $subject = delete $param->{subject}; + my $html_body = delete $param->{html_body}; + my $text_body = delete $param->{text_body}; + my $error = ''; + + my $job = delete $param->{'job'} + or die "email_search_result must run from the job queue.\n"; + + my $msg_template; + if ( $msgnum ) { + $msg_template = qsearchs('msg_template', { msgnum => $msgnum } ) + or die "msgnum $msgnum not found\n"; + } + + $param->{'payby'} = [ split(/\0/, $param->{'payby'}) ] + unless ref($param->{'payby'}); + + my $sql_query = $class->search($param->{'search'}); + + my $count_query = delete($sql_query->{'count_query'}); + my $count_sth = dbh->prepare($count_query) + or die "Error preparing $count_query: ". dbh->errstr; + $count_sth->execute + or die "Error executing $count_query: ". $count_sth->errstr; + my $count_arrayref = $count_sth->fetchrow_arrayref; + my $num_cust = $count_arrayref->[0]; + + my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo + my @retry_jobs = (); + my $success = 0; + + #eventually order+limit magic to reduce memory use? + foreach my $obj ( qsearch($sql_query) ) { + + #progressbar first, so that the count is right + $num++; + if ( time - $min_sec > $last ) { + my $error = $job->update_statustext( + int( 100 * $num / $num_cust ) + ); + die $error if $error; + $last = time; + } + + my $cust_main = $obj->cust_main; + my @message; + if ( !$cust_main ) { + next; # unlinked object; nothing else we can do + } + + if ( $msg_template ) { + # XXX add support for other context objects? + @message = $msg_template->prepare( 'cust_main' => $cust_main ); + } + else { + my $to = $cust_main->invoicing_list_emailonly_scalar; + next if !$to; + + @message = ( + 'from' => $from, + 'to' => $to, + 'subject' => $subject, + 'html_body' => $html_body, + 'text_body' => $text_body, + ); + } #if $msg_template + + $error = send_email( generate_email( @message ) ); + + if($error) { + # queue the sending of this message so that the user can see what we + # tried to do, and retry if desired + my $queue = new FS::queue { + 'job' => 'FS::Misc::process_send_email', + 'custnum' => $cust_main->custnum, + 'status' => 'failed', + 'statustext' => $error, + }; + $queue->insert(@message); + push @retry_jobs, $queue; + } + else { + $success++; + } + + if($success == 0 and + (scalar(@retry_jobs) > 10 or $num == $num_cust) + ) { + # 10 is arbitrary, but if we have enough failures, that's + # probably a configuration or network problem, and we + # abort the batch and run away screaming. + # We NEVER do this if anything was successfully sent. + $_->delete foreach (@retry_jobs); + return "multiple failures: '$error'\n"; + } + } # foreach $obj + + if(@retry_jobs) { + # fail the job, but with a status message that makes it clear + # something was sent. + return "Sent $success, failed ".scalar(@retry_jobs).". Failed attempts placed in job queue.\n"; + } + + return ''; +} + +sub process_email_search_result { + my $job = shift; + #warn "$me process_re_X $method for job $job\n" if $DEBUG; + + my $param = thaw(decode_base64(shift)); + warn Dumper($param) if $DEBUG; + + $param->{'job'} = $job; + + $param->{'search'} = thaw(decode_base64($param->{'search'})) + or die "process_email_search_result requires search params.\n"; + +# $param->{'payby'} = [ split(/\0/, $param->{'payby'}) ] +# unless ref($param->{'payby'}); + + my $table = $param->{'table'} + or die "process_email_search_result requires table.\n"; + + eval "use FS::$table;"; + die "error loading FS::$table: $@\n" if $@; + + my $error = "FS::$table"->email_search_result( $param ); + die $error if $error; + +} + =back =head1 BUGS diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index 74cedfc77..5ffe0e452 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -113,6 +113,126 @@ sub table { 'svc_broadband'; } sub table_dupcheck_fields { ( 'mac_addr' ); } +=item search HASHREF + +Class method which returns a qsearch hash expression to search for parameters +specified in HASHREF. + +Parameters: + +=over 4 + +=item unlinked - set to search for all unlinked services. Overrides all other options. + +=item agentnum + +=item custnum + +=item svcpart + +=item ip_addr + +=item pkgpart - arrayref + +=item routernum - arrayref + +=item order_by + +=back + +=cut + +sub search { + my ($class, $params) = @_; + my @where = (); + my @from = ( + 'LEFT JOIN cust_svc USING ( svcnum )', + 'LEFT JOIN part_svc USING ( svcpart )', + 'LEFT JOIN cust_pkg USING ( pkgnum )', + 'LEFT JOIN cust_main USING ( custnum )', + ); + + # based on FS::svc_acct::search, probably the most mature of the bunch + #unlinked + push @where, 'pkgnum IS NULL' if $params->{'unlinked'}; + + #agentnum + if ( $params->{'agentnum'} =~ /^(\d+)$/ and $1 ) { + push @where, "agentnum = $1"; + } + push @where, $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null_right' => 'View/link unlinked services', + 'table' => 'cust_main' + ); + + #custnum + if ( $params->{'custnum'} =~ /^(\d+)$/ and $1 ) { + push @where, "custnum = $1"; + } + + #pkgpart, now properly untainted, can be arrayref + for my $pkgpart ( $params->{'pkgpart'} ) { + if ( ref $pkgpart ) { + my $where = join(',', map { /^(\d+)$/ ? $1 : () } @$pkgpart ); + push @where, "cust_pkg.pkgpart IN ($where)" if $where; + } + elsif ( $pkgpart =~ /^(\d+)$/ ) { + push @where, "cust_pkg.pkgpart = $1"; + } + } + + #routernum, can be arrayref + for my $routernum ( $params->{'routernum'} ) { + push @from, 'LEFT JOIN addr_block USING ( blocknum )'; + if ( ref $routernum and grep { $_ } @$routernum ) { + my $where = join(',', map { /^(\d+)$/ ? $1 : () } @$routernum ); + push @where, "addr_block.routernum IN ($where)" if $where; + } + elsif ( $routernum =~ /^(\d+)$/ ) { + push @where, "addr_block.routernum = $1"; + } + } + + #svcnum + if ( $params->{'svcnum'} =~ /^(\d+)$/ ) { + push @where, "svcnum = $1"; + } + + #svcpart + if ( $params->{'svcpart'} =~ /^(\d+)$/ ) { + push @where, "svcpart = $1"; + } + + #ip_addr + if ( $params->{'ip_addr'} =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/ ) { + push @where, "ip_addr = '$1'"; + } + + #custnum + if ( $params->{'custnum'} =~ /^(\d+)$/ and $1) { + push @where, "custnum = $1"; + } + + my $addl_from = join(' ', @from); + my $extra_sql = ''; + $extra_sql = 'WHERE '.join(' AND ', @where) if @where; + my $count_query = "SELECT COUNT(*) FROM svc_broadband $addl_from $extra_sql"; + return( { + 'table' => 'svc_broadband', + 'hashref' => {}, + 'select' => join(', ', + 'svc_broadband.*', + 'part_svc.svc', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields($params->{'cust_fields'}), + ), + 'extra_sql' => $extra_sql, + 'addl_from' => $addl_from, + 'order_by' => "ORDER BY ".($params->{'order_by'} || 'svcnum'), + 'count_query' => $count_query, + } ); +} + =item search_sql STRING Class method which returns an SQL fragment to search for the given string. |