[freeside-commits] branch FREESIDE_3_BRANCH updated. 4137519a5a1a9380c59385536883e3e228627ee8

Mark Wells mark at 420.am
Tue May 3 13:51:38 PDT 2016


The branch, FREESIDE_3_BRANCH has been updated
       via  4137519a5a1a9380c59385536883e3e228627ee8 (commit)
       via  eb1796299a429ba5cd3b939a65bf5e95abf0086c (commit)
       via  755159a8654a2eda89badd1498f8def3a472cb15 (commit)
      from  5b2b242ad80a2efac3fb3f4d919142307084bd73 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 4137519a5a1a9380c59385536883e3e228627ee8
Merge: eb17962 5b2b242
Author: Mark Wells <mark at freeside.biz>
Date:   Tue May 3 13:50:55 2016 -0700

    Merge branch 'FREESIDE_3_BRANCH' of git.freeside.biz:/home/git/freeside into 3.x


commit eb1796299a429ba5cd3b939a65bf5e95abf0086c
Author: Mark Wells <mark at freeside.biz>
Date:   Tue May 3 12:52:21 2016 -0700

    email to specific contact classes, 3.x adjustments

diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm
index e425c4a..eedc736 100644
--- a/FS/FS/Misc.pm
+++ b/FS/FS/Misc.pm
@@ -256,6 +256,13 @@ sub send_email {
   push @to, $options{bcc} if defined($options{bcc});
   # make sure 
   my @env_to = split(/\s*,\s*/, join(', ', @to));
+  # strip display-name from envelope addresses
+  foreach (@env_to) {
+    s/^\s*//;
+    s/\s*$//;
+    s/^(.*)\s*<(.*@.*)>$/$2/;
+  }
+
   local $@; # just in case
   eval { sendmail($message, { transport => $transport,
                               from      => $from,
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index ef3ab61..41b2743 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -3367,9 +3367,7 @@ sub invoicing_list_emailonly_scalar {
 
 Returns a list of contacts (L<FS::contact> 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.
+classes. If '0' is given, also returns contacts with no class.
 
 If no arguments are given, returns all contacts for the customer.
 
@@ -3379,18 +3377,15 @@ 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,
+    select      => 'contact.*',
+    extra_sql   => ' WHERE 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';
+    if ( $_ eq '0' ) {
+      push @orwhere, 'contact.classnum is null';
     } elsif ( /^\d+$/ ) {
       push @classnums, $_;
     } else {
@@ -3399,7 +3394,7 @@ sub contact_list {
   }
 
   if (@classnums) {
-    push @orwhere, 'cust_contact.classnum IN ('.join(',', @classnums).')';
+    push @orwhere, 'contact.classnum IN ('.join(',', @classnums).')';
   }
   if (@orwhere) {
     $search->{extra_sql} .= ' AND (' .
@@ -3413,21 +3408,44 @@ sub contact_list {
 =item contact_list_email [ CLASSNUM, ... ]
 
 Same as L</contact_list>, but returns email destinations instead of contact
-objects.
+objects. Also accepts 'invoice' as an argument, in which case this will also
+return the invoice email address if any.
 
 =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 . '>';
+  my @classnums;
+  my $and_invoice;
+  foreach (@_) {
+    if (/^invoice$/) {
+      $and_invoice = 1;
+    } else {
+      push @classnums, $_;
+    }
+  }
+  my %emails;
+  # if the only argument passed was 'invoice' then no classnums are
+  # intended, so skip this.
+  if ( @classnums ) {
+    my @contacts = $self->contact_list(@classnums);
+    foreach my $contact (@contacts) {
+      foreach my $contact_email ($contact->contact_email) {
+        # unlike on 4.x, we have a separate list of invoice email
+        # destinations.
+        # make sure they're not redundant with contact emails
+        my $dest = $contact->firstlast . ' <' . $contact_email->emailaddress . '>';
+        $emails{ $contact_email->emailaddress } = $dest;
+      }
+    }
+  }
+  if ( $and_invoice ) {
+    foreach my $email ($self->invoicing_list_emailonly) {
+      my $dest = $self->name_short . ' <' . $email . '>';
+      $emails{ $email } ||= $dest;
     }
   }
-  @emails;
+  values %emails;
 }
 
 =item referral_custnum_cust_main
diff --git a/FS/FS/cust_main_Mixin.pm b/FS/FS/cust_main_Mixin.pm
index 94e6eaa..dee9aa8 100644
--- a/FS/FS/cust_main_Mixin.pm
+++ b/FS/FS/cust_main_Mixin.pm
@@ -469,9 +469,8 @@ sub email_search_result {
       # 3.x: false laziness with msg_template.pm; on 4.x, all email notices
       # are generated from templates and this case goes away
       my @classes;
-      if ( $opt{'to_contact_classnum'} ) {
-        my $classnum = $opt{'to_contact_classnum'};
-        @classes = ref($classnum) ? @$classnum : split(',', $classnum);
+      if ( $to_contact_classnum ) {
+        @classes = ref($to_contact_classnum) ? @$to_contact_classnum : split(',', $to_contact_classnum);
       }
       if (!@classes) {
         @classes = ( 'invoice' );
diff --git a/httemplate/misc/email-customers.html b/httemplate/misc/email-customers.html
index 11ab050..c9a216c 100644
--- a/httemplate/misc/email-customers.html
+++ b/httemplate/misc/email-customers.html
@@ -46,10 +46,10 @@ should be used to set msgnum or from/subject/html_body cgi params
 <INPUT TYPE="hidden" NAME="search" VALUE="<% encode_base64(nfreeze(\%search)) %>">
 <INPUT TYPE="hidden" NAME="popup" VALUE="<% $popup %>">
 <INPUT TYPE="hidden" NAME="url" VALUE="<% $url | h %>">
-<INPUT TYPE="hidden" NAME="to_contact_classnum" VALUE="<% join(',', @contact_classnum) %>">
 
 % if ( $cgi->param('action') eq 'send' ) { 
 
+    <INPUT TYPE="hidden" NAME="to_contact_classnum" VALUE="<% $cgi->param('to_contact_classnum') %>">
     <FONT SIZE="+2">Sending notice</FONT>
 
     <& /elements/progress-init.html,
@@ -62,6 +62,7 @@ should be used to set msgnum or from/subject/html_body cgi params
 
 % } elsif ( $cgi->param('action') eq 'preview' ) {
 
+    <INPUT TYPE="hidden" NAME="to_contact_classnum" VALUE="<% join(',', @contact_classnum) %>">
     <FONT SIZE="+2">Preview notice</FONT>
 
 % }
@@ -149,7 +150,6 @@ Template:
          onchange => 'toggle(this)',
     &>
     <BR>
-% }
 % # select destination contact classes
 Send to contacts:
   <& /elements/checkboxes.html,
@@ -306,8 +306,8 @@ if ( $cgi->param('action') eq 'preview' ) {
     ($from, $subject, $html_body) = @message{'from', 'subject', 'html_body'};
   }
 
-  # contact_class_X params
-  foreach my $param ( $cgi->multi_param ) {
+  # contact_class_X params in preview
+  foreach my $param ( $cgi->param ) {
     if ( $param =~ /^contact_class_(\w+)$/ ) {
       push @contact_classnum, $1;
       if ( $1 eq 'invoice' ) {
@@ -318,8 +318,10 @@ if ( $cgi->param('action') eq 'preview' ) {
       }
     }
   }
+
 }
 
+# and set up contact checkboxes for edit mode
 my @contact_checkboxes = (
   [ 'invoice' => { label => 'Invoice recipients' } ]
 );

commit 755159a8654a2eda89badd1498f8def3a472cb15
Author: Mark Wells <mark at freeside.biz>
Date:   Sat Apr 30 18:07:50 2016 -0700

    allow sending email to specific contact classes, #33316
    
    Conflicts:
    	FS/FS/cust_main_Mixin.pm
    	FS/FS/msg_template/email.pm
    	httemplate/misc/email-customers.html

diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index cfcd5f4..ef3ab61 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -3363,6 +3363,73 @@ sub invoicing_list_emailonly_scalar {
   join(', ', $self->invoicing_list_emailonly);
 }
 
+=item contact_list [ CLASSNUM, ... ]
+
+Returns a list of contacts (L<FS::contact> 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</contact_list>, 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 96e520d..94e6eaa 100644
--- a/FS/FS/cust_main_Mixin.pm
+++ b/FS/FS/cust_main_Mixin.pm
@@ -380,6 +380,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.
@@ -403,6 +409,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'}
@@ -455,10 +462,21 @@ sub email_search_result {
       %message = $msg_template->prepare(
         'cust_main' => $cust_main,
         'object'    => $obj,
+        'to_contact_classnum' => $to_contact_classnum,
       );
-    }
-    else {
-      my @to = $cust_main->invoicing_list_emailonly;
+
+    } else {
+      # 3.x: false laziness with msg_template.pm; on 4.x, all email notices
+      # are generated from templates and this case goes away
+      my @classes;
+      if ( $opt{'to_contact_classnum'} ) {
+        my $classnum = $opt{'to_contact_classnum'};
+        @classes = ref($classnum) ? @$classnum : split(',', $classnum);
+      }
+      if (!@classes) {
+        @classes = ( 'invoice' );
+      }
+      my @to = $cust_main->contact_list_email(@classes);
       next if !@to;
 
       %message = (
diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm
index 70e5569..50a9b3f 100644
--- a/FS/FS/msg_template.pm
+++ b/FS/FS/msg_template.pm
@@ -383,12 +383,26 @@ sub prepare {
 
   my @to;
   if ( exists($opt{'to'}) ) {
+
     @to = split(/\s*,\s*/, $opt{'to'});
+
+  } elsif ( $cust_main ) {
+
+    my @classes;
+    if ( $opt{'to_contact_classnum'} ) {
+      my $classnum = $opt{'to_contact_classnum'};
+      @classes = ref($classnum) ? @$classnum : split(',', $classnum);
+    }
+    if (!@classes) {
+      @classes = ( 'invoice' );
+    }
+    @to = $cust_main->contact_list_email(@classes);
+
+  } else {
+
+    die 'no To: address or cust_main object specified';
+
   }
-  else {
-    @to = $cust_main->invoicing_list_emailonly;
-  }
-  # no warning when preparing with no destination
 
   my $from_addr = $self->from_addr;
 
diff --git a/httemplate/elements/checkboxes.html b/httemplate/elements/checkboxes.html
index 69ef18f..1f34224 100644
--- a/httemplate/elements/checkboxes.html
+++ b/httemplate/elements/checkboxes.html
@@ -27,7 +27,7 @@ Example:
 
 </%doc>
 
-<TABLE CELLSPACING=0 CELLPADDING=0>
+<TABLE CELLSPACING=0 CELLPADDING=0 <% $style %>>
 
 % 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'} . '"';
+}
 </%init>
diff --git a/httemplate/misc/email-customers.html b/httemplate/misc/email-customers.html
index cd4c92f..11ab050 100644
--- a/httemplate/misc/email-customers.html
+++ b/httemplate/misc/email-customers.html
@@ -6,8 +6,8 @@ frozen hash in the 'search' cgi param.  Form allows selecting an existing msg_te
 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')
@@ -46,6 +46,7 @@ should be used to set msgnum or from/subject/html_body cgi params
 <INPUT TYPE="hidden" NAME="search" VALUE="<% encode_base64(nfreeze(\%search)) %>">
 <INPUT TYPE="hidden" NAME="popup" VALUE="<% $popup %>">
 <INPUT TYPE="hidden" NAME="url" VALUE="<% $url | h %>">
+<INPUT TYPE="hidden" NAME="to_contact_classnum" VALUE="<% join(',', @contact_classnum) %>">
 
 % if ( $cgi->param('action') eq 'send' ) { 
 
@@ -53,7 +54,8 @@ should be used to set msgnum or from/subject/html_body cgi params
 
     <& /elements/progress-init.html,
                  'OneTrueForm',
-                 [ qw( search table from subject html_body text_body msgnum ) ],
+                 [ qw( search table from subject html_body text_body
+                        msgnum to_contact_classnum ) ],
                  $process_url,
                  $pdest,
     &>
@@ -68,7 +70,6 @@ should be used to set msgnum or from/subject/html_body cgi params
 
     <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
     <INPUT TYPE="hidden" NAME="msgnum" VALUE="<% scalar($cgi->param('msgnum')) %>">
-
 %   if ( $msg_template ) {
       <% include('/elements/tr-fixed.html',
                    'label'      => 'Template:',
@@ -84,6 +85,10 @@ should be used to set msgnum or from/subject/html_body cgi params
                 )
       %>
 
+      <& /elements/tr-td-label.html, 'label' => 'To contacts:' &>
+        <td><% join('<BR>', @contact_classname) %></td>
+      </tr>
+
       <% include('/elements/tr-fixed.html',
                    'field'      => 'subject',
                    'label'      => 'Subject:',
@@ -144,6 +149,21 @@ Template:
          onchange => 'toggle(this)',
     &>
     <BR>
+% }
+% # 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
+    },
+  &>
+<BR>
+% # if sending a one-off message, show a form to edit it
   <TABLE BGCOLOR="#cccccc" CELLSPACING=0 WIDTH="100%" id="table_no_template">
     <& /elements/tr-td-label.html, 'label' => 'From:' &>
       <TD><& /elements/input-text.html,
@@ -250,6 +270,12 @@ if ( $cgi->param('from') ) {
 my $subject = $cgi->param('subject') || '';
 my $html_body = $cgi->param('html_body') || '';
 
+my @contact_classnum;
+my @contact_classname;
+
+my $subject = $cgi->param('subject');
+my $body = $cgi->param('body');
+
 my $msg_template = '';
 
 if ( $cgi->param('action') eq 'preview' ) {
@@ -279,6 +305,28 @@ if ( $cgi->param('action') eq 'preview' ) {
     my %message = $msg_template->prepare(%msgopts);
     ($from, $subject, $html_body) = @message{'from', 'subject', 'html_body'};
   }
+
+  # 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 }
+  ];
+}
 </%init>

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/Misc.pm                        |    7 +++
 FS/FS/cust_main.pm                   |   85 ++++++++++++++++++++++++++++++++++
 FS/FS/cust_main_Mixin.pm             |   23 +++++++--
 FS/FS/msg_template.pm                |   22 +++++++--
 httemplate/elements/checkboxes.html  |    6 ++-
 httemplate/misc/email-customers.html |   58 +++++++++++++++++++++--
 6 files changed, 189 insertions(+), 12 deletions(-)




More information about the freeside-commits mailing list