From: Mark Wells Date: Sun, 1 May 2016 01:07:50 +0000 (-0700) Subject: allow sending email to specific contact classes, #33316 X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=770d8237cda1b6459962d31f6ae86997ad5843be allow sending email to specific contact classes, #33316 --- diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 38edd0493..4ec5a02f7 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -2877,6 +2877,73 @@ sub invoicing_list_emailonly_scalar { join(', ', $self->invoicing_list_emailonly); } +=item contact_list [ CLASSNUM, ... ] + +Returns a list of contacts (L objects) for the customer. If +a list of contact classnums is given, returns only contacts in those +classes. If the pseudo-classnum 'invoice' is given, returns contacts that +are marked as invoice destinations. If '0' is given, also returns contacts +with no class. + +If no arguments are given, returns all contacts for the customer. + +=cut + +sub contact_list { + my $self = shift; + my $search = { + table => 'contact', + select => 'contact.*, cust_contact.invoice_dest', + addl_from => ' JOIN cust_contact USING (contactnum)', + extra_sql => ' WHERE cust_contact.custnum = '.$self->custnum, + }; + + my @orwhere; + my @classnums; + foreach (@_) { + if ( $_ eq 'invoice' ) { + push @orwhere, 'cust_contact.invoice_dest = \'Y\''; + } elsif ( $_ eq '0' ) { + push @orwhere, 'cust_contact.classnum is null'; + } elsif ( /^\d+$/ ) { + push @classnums, $_; + } else { + die "bad classnum argument '$_'"; + } + } + + if (@classnums) { + push @orwhere, 'cust_contact.classnum IN ('.join(',', @classnums).')'; + } + if (@orwhere) { + $search->{extra_sql} .= ' AND (' . + join(' OR ', map "( $_ )", @orwhere) . + ')'; + } + + qsearch($search); +} + +=item contact_list_email [ CLASSNUM, ... ] + +Same as L, but returns email destinations instead of contact +objects. + +=cut + +sub contact_list_email { + my $self = shift; + my @contacts = $self->contact_list(@_); + my @emails; + foreach my $contact (@contacts) { + foreach my $contact_email ($contact->contact_email) { + push @emails, + $contact->firstlast . ' <' . $contact_email->emailaddress . '>'; + } + } + @emails; +} + =item referral_custnum_cust_main Returns the customer who referred this customer (or the empty string, if diff --git a/FS/FS/cust_main_Mixin.pm b/FS/FS/cust_main_Mixin.pm index bbba8c5f7..9fc66e059 100644 --- a/FS/FS/cust_main_Mixin.pm +++ b/FS/FS/cust_main_Mixin.pm @@ -383,6 +383,12 @@ HTML body Text body +=item to_contact_classnum + +The customer contact class (or classes, as a comma-separated list) to send +the message to. If unspecified, will be sent to any contacts that are marked +as invoice destinations (the equivalent of specifying 'invoice'). + =back Returns an error message, or false for success. @@ -406,6 +412,7 @@ sub email_search_result { my $subject = delete $param->{subject}; my $html_body = delete $param->{html_body}; my $text_body = delete $param->{text_body}; + my $to_contact_classnum = delete $param->{to_contact_classnum}; my $error = ''; my $job = delete $param->{'job'} @@ -471,6 +478,7 @@ sub email_search_result { my $cust_msg = $msg_template->prepare( 'cust_main' => $cust_main, 'object' => $obj, + 'to_contact_classnum' => $to_contact_classnum, ); # For non-cust_main searches, we avoid duplicates based on message diff --git a/FS/FS/msg_template/email.pm b/FS/FS/msg_template/email.pm index 07a1fa093..d1df5d61c 100644 --- a/FS/FS/msg_template/email.pm +++ b/FS/FS/msg_template/email.pm @@ -289,9 +289,25 @@ sub prepare { my @to; if ( exists($opt{'to'}) ) { + @to = split(/\s*,\s*/, $opt{'to'}); + } elsif ( $cust_main ) { - @to = $cust_main->invoicing_list_emailonly; + + if ( $opt{'to_contact_classnum'} ) { + + my $classnum = $opt{'to_contact_classnum'}; + my @classes = ref($classnum) ? @$classnum : split(',', $classnum); + if ( !@classes ) { + # traditional behavior: send to invoice email destinations (only) + @classes = ( 'invoice' ); + } + @to = $cust_main->contact_list_email(@classes); + # not guaranteed to produce contacts, but then customers aren't + # guaranteed to have email addresses on file. in that case, env_to + # will be null and sending this message will fail. + } + } else { die 'no To: address or cust_main object specified'; } @@ -324,13 +340,16 @@ sub prepare { ); warn "$me creating message headers\n" if $DEBUG; + # strip display-name from envelope addresses + # (use Email::Address for this? it chokes on non-ASCII characters in + # the display-name, which is not great for us) my $env_from = $from_addr; - $env_from =~ s/^\s*//; $env_from =~ s/\s*$//; - if ( $env_from =~ /^(.*)\s*<(.*@.*)>$/ ) { - # a common idiom - $env_from = $2; - } - + foreach ($env_from, @to) { + s/^\s*//; + s/\s*$//; + s/^(.*)\s*<(.*@.*)>$/$2/; + } + my $domain; if ( $env_from =~ /\@([\w\.\-]+)/ ) { $domain = $1; diff --git a/httemplate/elements/checkboxes.html b/httemplate/elements/checkboxes.html index ad9d691b9..b07b6545f 100644 --- a/httemplate/elements/checkboxes.html +++ b/httemplate/elements/checkboxes.html @@ -27,7 +27,7 @@ Example: - +
> % unless ( $opt{'disable_links'} ) { @@ -108,4 +108,8 @@ $opt{'error_checked_callback'} ||= sub { $cgi->param($opt{'element_name_prefix'}. $name ); }; +my $style = ''; +if ($opt{'style'}) { + $style = 'STYLE="' . $opt{'style'} . '"'; +} diff --git a/httemplate/misc/email-customers.html b/httemplate/misc/email-customers.html index 8e2863455..d086c676d 100644 --- a/httemplate/misc/email-customers.html +++ b/httemplate/misc/email-customers.html @@ -7,8 +7,8 @@ selecting an existing msg_template, or creating a custom message, and shows a preview of the message before sending. If linked to as a popup, include the cgi parameter 'popup' for proper header handling. -This may also be used as an element in other pages, enabling you to provide an -alternate initial form while using this for search freezing/thawing and +This may also be used as an element in other pages, enabling you to provide +an alternate initial form while using this for search freezing/thawing and preview/send actions, with the following options: acl - the access right to use (defaults to 'Bulk send customer notices') @@ -48,6 +48,7 @@ from/subject/body cgi params + % if ( $cgi->param('preview') ) { % # preview mode: at this point we have a msg_template (either "real" or @@ -57,7 +58,7 @@ from/subject/body cgi params Preview notice <& /elements/progress-init.html, 'OneTrueForm', - [ qw( search table msgnum ) ], + [ qw( search table msgnum to_contact_classnum ) ], $process_url, $pdest, &> @@ -79,6 +80,10 @@ from/subject/body cgi params + <& /elements/tr-td-label.html, 'label' => 'To contacts:' &> + + + <& /elements/tr-td-label.html, 'label' => 'Subject:' &> @@ -158,6 +163,20 @@ Template: &>
% } +% # select destination contact classes +Send to contacts: + <& /elements/checkboxes.html, + 'style' => 'display: inline; vertical-align: top', + 'disable_links' => 1, + 'names_list' => \@contact_checkboxes, + 'element_name_prefix' => 'contact_class_', + 'checked_callback' => sub { + my($cgi, $name) = @_; + $name eq 'invoice' #others default to unchecked + }, + &> +
+% # if sending a one-off message, show a form to edit it
<% $from |h %>
<% join('
', @contact_classname) %>
<% $subject |h %>
<& /elements/tr-td-label.html, 'label' => 'From:' &>
<& /elements/input-text.html, @@ -262,6 +281,9 @@ if ( $cgi->param('msgnum') =~ /^(\d+)$/ ) { or die "template not found: ".$cgi->param('msgnum'); } +my @contact_classnum; +my @contact_classname; + my $subject = $cgi->param('subject'); my $body = $cgi->param('body'); my ($html_body, $text_body); @@ -337,6 +359,28 @@ if ( !$cgi->param('preview') ) { $subject = $1; } } + + # contact_class_X params + foreach my $param ( $cgi->multi_param ) { + if ( $param =~ /^contact_class_(\w+)$/ ) { + push @contact_classnum, $1; + if ( $1 eq 'invoice' ) { + push @contact_classname, 'Invoice recipients'; + } else { + my $contact_class = FS::contact_class->by_key($1); + push @contact_classname, encode_entities($contact_class->classname); + } + } + } } +my @contact_checkboxes = ( + [ 'invoice' => { label => 'Invoice recipients' } ] +); +foreach my $class (qsearch('contact_class', { disabled => '' })) { + push @contact_checkboxes, [ + $class->classnum, + { label => $class->classname } + ]; +}