summaryrefslogtreecommitdiff
path: root/FS
diff options
context:
space:
mode:
authorivan <ivan>2008-06-17 03:35:56 +0000
committerivan <ivan>2008-06-17 03:35:56 +0000
commit7153190ee1bfeb6d3ad9e6da270a41a949333a7e (patch)
tree4f3f13115ed182e1d219c136f559eb2fa04a1183 /FS
parent97c8cbe1a7645ecce1192a1c3c20807e428f907c (diff)
finish adding a feature to easily list all email addresses for an agent & send them email
Diffstat (limited to 'FS')
-rw-r--r--FS/FS/AccessRight.pm3
-rw-r--r--FS/FS/ConfDefaults.pm3
-rw-r--r--FS/FS/Misc.pm164
-rw-r--r--FS/FS/cust_main.pm118
4 files changed, 276 insertions, 12 deletions
diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm
index 13dbd7f..5621a97 100644
--- a/FS/FS/AccessRight.pm
+++ b/FS/FS/AccessRight.pm
@@ -100,6 +100,7 @@ tie my %rights, 'Tie::IxHash',
'Add customer note', #NEW
'Edit customer note', #NEW
'Bill customer now', #NEW
+ 'Bulk send customer notices', #NEW
],
###
@@ -193,7 +194,7 @@ tie my %rights, 'Tie::IxHash',
###
# report/listing rights...
###
- 'Reprting/listing rights' => [
+ 'Reporting/listing rights' => [
'List customers',
'List zip codes', #NEW
'List invoices',
diff --git a/FS/FS/ConfDefaults.pm b/FS/FS/ConfDefaults.pm
index 7978259..a3af52d 100644
--- a/FS/FS/ConfDefaults.pm
+++ b/FS/FS/ConfDefaults.pm
@@ -56,6 +56,9 @@ sub cust_fields_avail { (
'Cust# | Cust. Status | Name | Company | Address 1 | Address 2 | City | State | Zip | Country | Day phone | Night phone | Fax number | Invoicing email(s) | Payment Type | Current Balance' =>
'custnum | Status | Last, First | Company | (all address fields ) | ( all phones ) | Invoicing email(s) | Payment Type | Current Balance',
+ 'Invoicing email(s)' => 'Invoicing email(s)',
+ 'Cust# | Invoicing email(s)' => 'custnum | Invoicing email(s)',
+
); }
=back
diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm
index 1f6eece..936f94a 100644
--- a/FS/FS/Misc.pm
+++ b/FS/FS/Misc.pm
@@ -12,7 +12,7 @@ use IPC::Run3; # for do_print... should just use IPC::Run i guess
#instead
@ISA = qw( Exporter );
-@EXPORT_OK = qw( send_email send_fax
+@EXPORT_OK = qw( generate_email send_email send_fax
states_hash counties state_label
card_types
generate_ps generate_pdf do_print
@@ -40,29 +40,173 @@ but are collected here to elimiate code duplication.
=over 4
+=item generate_email OPTION => VALUE ...
+
+Options:
+
+=item from
+
+Sender address, required
+
+=item to
+
+Recipient address, required
+
+=item subject
+
+email subject, required
+
+=item html_body
+
+Email body (HTML alternative). Arrayref of lines, or scalar.
+
+Will be placed inside an HTML <BODY> tag.
+
+=item text_body
+
+Email body (Text alternative). Arrayref of lines, or scalar.
+
+=back
+
+Returns an argument list to be passsed to L<send_email>.
+
+=cut
+
+#false laziness w/FS::cust_bill::generate_email
+
+use MIME::Entity;
+use HTML::Entities;
+
+sub generate_email {
+ my %args = @_;
+
+ my $me = '[FS::Misc::generate_email]';
+
+ my %return = (
+ 'from' => $args{'from'},
+ 'to' => $args{'to'},
+ 'subject' => $args{'subject'},
+ );
+
+ #if (ref($args{'to'}) eq 'ARRAY') {
+ # $return{'to'} = $args{'to'};
+ #} else {
+ # $return{'to'} = [ grep { $_ !~ /^(POST|FAX)$/ }
+ # $self->cust_main->invoicing_list
+ # ];
+ #}
+
+ warn "$me creating HTML/text multipart message"
+ if $DEBUG;
+
+ $return{'nobody'} = 1;
+
+ my $alternative = build MIME::Entity
+ 'Type' => 'multipart/alternative',
+ 'Encoding' => '7bit',
+ 'Disposition' => 'inline'
+ ;
+
+ my $data;
+ if ( ref($args{'text_body'}) eq 'ARRAY' ) {
+ $data = $args{'text_body'};
+ } else {
+ $data = [ split(/\n/, $args{'text_body'}) ];
+ }
+
+ $alternative->attach(
+ 'Type' => 'text/plain',
+ #'Encoding' => 'quoted-printable',
+ 'Encoding' => '7bit',
+ 'Data' => $data,
+ 'Disposition' => 'inline',
+ );
+
+ my @html_data;
+ if ( ref($args{'html_body'}) eq 'ARRAY' ) {
+ @html_data = @{ $args{'html_body'} };
+ } else {
+ @html_data = split(/\n/, $args{'html_body'});
+ }
+
+ $alternative->attach(
+ 'Type' => 'text/html',
+ 'Encoding' => 'quoted-printable',
+ 'Data' => [ '<html>',
+ ' <head>',
+ ' <title>',
+ ' '. encode_entities($return{'subject'}),
+ ' </title>',
+ ' </head>',
+ ' <body bgcolor="#e8e8e8">',
+ @html_data,
+ ' </body>',
+ '</html>',
+ ],
+ 'Disposition' => 'inline',
+ #'Filename' => 'invoice.pdf',
+ );
+
+ #no other attachment:
+ # multipart/related
+ # multipart/alternative
+ # text/plain
+ # text/html
+
+ $return{'content-type'} = 'multipart/related';
+ $return{'mimeparts'} = [ $alternative ];
+ $return{'type'} = 'multipart/alternative'; #Content-Type of first part...
+ #$return{'disposition'} = 'inline';
+
+ %return;
+
+}
+
=item send_email OPTION => VALUE ...
Options:
-I<from> - (required)
+=over 4
+
+=item from
+
+(required)
+
+=item to
+
+(required) comma-separated scalar or arrayref of recipients
+
+=item subject
-I<to> - (required) comma-separated scalar or arrayref of recipients
+(required)
-I<subject> - (required)
+=item content-type
-I<content-type> - (optional) MIME type for the body
+(optional) MIME type for the body
-I<body> - (required unless I<nobody> is true) arrayref of body text lines
+=item body
-I<mimeparts> - (optional, but required if I<nobody> is true) arrayref of MIME::Entity->build PARAMHASH refs or MIME::Entity objects. These will be passed as arguments to MIME::Entity->attach().
+(required unless I<nobody> is true) arrayref of body text lines
-I<nobody> - (optional) when set true, send_email will ignore the I<body> option and simply construct a message with the given I<mimeparts>. In this case,
+=item mimeparts
+
+(optional, but required if I<nobody> is true) arrayref of MIME::Entity->build PARAMHASH refs or MIME::Entity objects. These will be passed as arguments to MIME::Entity->attach().
+
+=item nobody
+
+(optional) when set true, send_email will ignore the I<body> option and simply construct a message with the given I<mimeparts>. In this case,
I<content-type>, if specified, overrides the default "multipart/mixed" for the outermost MIME container.
-I<content-encoding> - (optional) when using nobody, optional top-level MIME
+=item content-encoding
+
+(optional) when using nobody, optional top-level MIME
encoding which, if specified, overrides the default "7bit".
-I<type> - (optional) type parameter for multipart/related messages
+=item type
+
+(optional) type parameter for multipart/related messages
+
+=back
=cut
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 33788f3..946495c 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -22,7 +22,7 @@ use Locale::Country;
use Data::Dumper;
use FS::UID qw( getotaker dbh driver_name );
use FS::Record qw( qsearchs qsearch dbdef );
-use FS::Misc qw( send_email generate_ps do_print );
+use FS::Misc qw( generate_email send_email generate_ps do_print );
use FS::Msgcat qw(gettext);
use FS::cust_pkg;
use FS::cust_svc;
@@ -5470,6 +5470,122 @@ sub search_sql {
}
+=item email_search_sql HASHREF
+
+(Class method)
+
+Emails a notice to the specified customers.
+
+Valid parameters are those of the L<search_sql> 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_sql {
+ 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 $job = delete $params->{'job'};
+
+ my $sql_query = $class->search_sql($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
+
+ #eventually order+limit magic to reduce memory use?
+ foreach my $cust_main ( qsearch($sql_query) ) {
+
+ my $to = $cust_main->invoicing_list_emailonly_scalar;
+ next unless $to;
+
+ my $error = send_email(
+ generate_email(
+ 'from' => $from,
+ 'to' => $to,
+ 'subject' => $subject,
+ 'html_body' => $html_body,
+ 'text_body' => $text_body,
+ )
+ );
+ return $error if $error;
+
+ if ( $job ) { #progressbar foo
+ $num++;
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $num / $num_cust )
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
+ }
+
+ return '';
+}
+
+use Storable qw(thaw);
+use Data::Dumper;
+use MIME::Base64;
+sub process_email_search_sql {
+ 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;
+
+ my $error = FS::cust_main->email_search_sql( $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