[freeside-commits] branch master updated. e803d5f00368e951f7a4b82d5d390b53c4a6c827
Mitch Jackson
mitch at freeside.biz
Tue Jan 30 07:30:18 PST 2018
The branch, master has been updated
via e803d5f00368e951f7a4b82d5d390b53c4a6c827 (commit)
from 280c44682a79e586af941e869e7a78ca8f367cf2 (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 e803d5f00368e951f7a4b82d5d390b53c4a6c827
Author: Mitch Jackson <mitch at freeside.biz>
Date: Tue Jan 30 09:27:42 2018 -0600
RT# 73422 Changes to report Customer Contacts
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index a82d8a225..7c9868d7a 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -3072,14 +3072,15 @@ sub contact_list {
# WHERE ...
# AND (
- # ( cust_contact.classnum IN (1,2,3) )
- # OR
- # ( cust_contact.classnum IS NULL )
- #
+ # (
+ # cust_contact.classnum IN (1,2,3)
+ # OR
+ # cust_contact.classnum IS NULL
+ # )
# AND (
- # ( cust_contact.invoice_dest = 'Y' )
+ # cust_contact.invoice_dest = 'Y'
# OR
- # ( cust_contact.message_dest = 'Y' )
+ # cust_contact.message_dest = 'Y'
# )
# )
@@ -3105,12 +3106,14 @@ sub contact_list {
$search->{extra_sql} .= ' AND ( ';
if (@or_classnum) {
- $search->{extra_sql} .= join ' OR ', map {" ($_) "} @or_classnum;
+ $search->{extra_sql} .= ' ( ';
+ $search->{extra_sql} .= join ' OR ', map {" $_ "} @or_classnum;
+ $search->{extra_sql} .= ' ) ';
$search->{extra_sql} .= ' AND ( ' if @and_dest;
}
if (@and_dest) {
- $search->{extra_sql} .= join ' OR ', map {" ($_) "} @and_dest;
+ $search->{extra_sql} .= join ' OR ', map {" $_ "} @and_dest;
$search->{extra_sql} .= ' ) ' if @or_classnum;
}
diff --git a/FS/FS/cust_main/Search.pm b/FS/FS/cust_main/Search.pm
index 2ec87cd14..815304bb4 100644
--- a/FS/FS/cust_main/Search.pm
+++ b/FS/FS/cust_main/Search.pm
@@ -1,6 +1,7 @@
package FS::cust_main::Search;
use strict;
+use Carp qw( croak );
use base qw( Exporter );
use vars qw( @EXPORT_OK $DEBUG $me $conf @fuzzyfields );
use String::Approx qw(amatch);
@@ -804,15 +805,51 @@ sub search {
unless $params->{'cancelled_pkgs'};
##
- # "with email address(es)" checkbox
+ # "with email address(es)" checkbox,
+ # also optionally: with_email_dest and with_contact_type
##
- push @where,
- 'EXISTS ( SELECT 1 FROM contact_email
+ if ($params->{with_email}) {
+ my @email_dest;
+ my $email_dest_sql;
+ my $contact_type_sql;
+
+ if ($params->{with_email_dest}) {
+ croak unless ref $params->{with_email_dest} eq 'ARRAY';
+
+ @email_dest = @{$params->{with_email_dest}};
+ $email_dest_sql =
+ " AND ( ".
+ join(' OR ',map(" cust_contact.${_}_dest IS NOT NULL ", @email_dest)).
+ " ) ";
+ # Can't use message_dist = 'Y' because single quotes are escaped later
+ }
+ if ($params->{with_contact_type}) {
+ croak unless ref $params->{with_contact_type} eq 'ARRAY';
+
+ my @contact_type = grep {/^\d+$/ && $_ > 0} @{$params->{with_contact_type}};
+ my $has_null_type = 0;
+ $has_null_type = 1 if grep { $_ eq 0 } @{$params->{with_contact_type}};
+ my $hnt_sql;
+ if ($has_null_type) {
+ $hnt_sql = ' OR ' if @contact_type;
+ $hnt_sql .= ' cust_contact.classnum IS NULL ';
+ }
+
+ $contact_type_sql =
+ " AND ( ".
+ join(' OR ', map(" cust_contact.classnum = $_ ", @contact_type)).
+ $hnt_sql.
+ " ) ";
+ }
+ push @where,
+ "EXISTS ( SELECT 1 FROM contact_email
JOIN cust_contact USING (contactnum)
WHERE cust_contact.custnum = cust_main.custnum
- )'
- if $params->{'with_email'};
+ $email_dest_sql
+ $contact_type_sql
+ ) ";
+ }
##
# "with postal mail invoices" checkbox
@@ -1390,4 +1427,3 @@ L<FS::cust_main>, L<FS::Record>
=cut
1;
-
diff --git a/httemplate/elements/select-multiple-contact_class.html b/httemplate/elements/select-multiple-contact_class.html
new file mode 100644
index 000000000..81a71cc25
--- /dev/null
+++ b/httemplate/elements/select-multiple-contact_class.html
@@ -0,0 +1,21 @@
+<%doc>
+
+Display a multi-select box containing all Email Types listed in
+the contact_class table.
+
+NOTE:
+ Don't confuse "Contact Type" (contact_email.classnum) with
+ "Customer Class" (cust_main.classnum)
+
+</%doc>
+<% include( '/elements/select-table.html',
+ table => 'contact_class',
+ hashref => { disabled => '' },
+ name_col => 'classname',
+ field => 'classnum',
+ pre_options => [ 0 => '(No Type)' ],
+ multiple => 1,
+ all_selected => 1,
+ @_,
+ )
+%>
diff --git a/httemplate/elements/tr-select-multiple-contact_class.html b/httemplate/elements/tr-select-multiple-contact_class.html
new file mode 100644
index 000000000..5de129324
--- /dev/null
+++ b/httemplate/elements/tr-select-multiple-contact_class.html
@@ -0,0 +1,32 @@
+<%doc>
+
+ Displays Contact Types as a multi-select box.
+
+ If no non-disabled Contact Types have been defined in contact_class table,
+ renders a hidden input field with a blank value.
+
+</%doc>
+
+% if ($has_types) {
+<TR>
+ <TD ALIGN="right"><% $opt{'label'} || emt('Contact Type') %></TD>
+ <TD>
+ <% include( '/elements/select-multiple-contact_class.html', %opt ) %>
+ </TD>
+</TR>
+% } else {
+<INPUT TYPE="hidden" NAME="<% $opt{field} %>" VALUE="">
+% }
+
+<%init>
+
+my %opt = @_;
+$opt{field} ||= $opt{element_name} ||= 'classnum';
+
+my $has_types =()= qsearch({
+ table => 'contact_class',
+ hashref => { disabled => '' },
+ extra_sql => ' LIMIT 1 ',
+});
+
+</%init>
diff --git a/httemplate/misc/email-customers.html b/httemplate/misc/email-customers.html
index dc53f6d55..4520c7514 100644
--- a/httemplate/misc/email-customers.html
+++ b/httemplate/misc/email-customers.html
@@ -174,7 +174,7 @@ Template:
<& /elements/checkboxes.html,
'style' => 'display: inline; vertical-align: top',
'disable_links' => 1,
- 'names_list' => \@contact_checkboxes,
+ 'names_list' => \@optin_checkboxes,
'element_name_prefix' => 'contact_class_',
'checked_callback' => sub {
# Called for each checkbox
@@ -199,6 +199,27 @@ Template:
</div>
% }
</TD>
+% if (@active_classes) {
+</tr>
+<tr>
+<TD>Contact Type:</TD>
+<TD>
+ <div id="contactclassesdiv">
+ <& /elements/checkboxes.html,
+ 'style' => 'display: inline; vertical-align: top',
+ 'disable_links' => 1,
+ 'names_list' => \@classnum_checkboxes,
+ 'element_name_prefix' => 'contact_class_',
+ 'checked_callback' => sub {
+ # Called for each checkbox
+ # Return true to default as checked, false as unchecked
+ my($cgi, $name) = @_;
+ exists $classnum_ischecked{$name};
+ },
+ &>
+ </div>
+</TD>
+% }
</TR>
</TABLE>
<BR>
@@ -342,6 +363,21 @@ if ( !$cgi->param('preview') ) {
} else {
+ my @checked_email_dest;
+ my @checked_contact_type;
+ for ($cgi->param) {
+ if (/^contact_class_(.+)$/) {
+ my $f = $1;
+ if ($f eq 'invoice' || $f eq 'message') {
+ push @checked_email_dest, $f;
+ } elsif ( $f =~ /^\d+$/ ) {
+ push @checked_contact_type, $f;
+ }
+ }
+ }
+ $search{with_email_dest} = \@checked_email_dest if @checked_email_dest;
+ $search{with_contact_type} = \@checked_contact_type if @checked_contact_type;
+
my $sql_query = "FS::$table"->search(\%search);
my $count_query = delete($sql_query->{'count_query'});
my $count_sth = dbh->prepare($count_query)
@@ -391,6 +427,8 @@ if ( !$cgi->param('preview') ) {
$sql_query->{'select'} = "$table.*";
$sql_query->{'order_by'} = '';
my $object = qsearchs($sql_query);
+ # Could use better error handling here...
+ die "No customers match the search criteria" unless ref $object;
$cust = $object->cust_main;
my %msgopts = (
'cust_main' => $cust,
@@ -435,16 +473,35 @@ if ( !$cgi->param('preview') ) {
}
}
-my @contact_checkboxes = (
+# Build data structures for "Opt In" and "Contact Type" checkboxes
+#
+# By default, message recipients will be selected, this is a message.
+# By default, all Contact Types will be selected, but this may be
+# overridden by passing 'classnums' get/post values. If no contact
+# types have been defined, the option will not be presented.
+
+my @active_classes = qsearch(contact_class => {disabled => ''} );
+
+$CGI::LIST_CONTEXT_WARN = 0;
+my @classnums = grep{ /^\d+$/ } $cgi->param('classnums');
+
+my %classnum_ischecked;
+if (@classnums) {
+ # values passed to form
+ $classnum_ischecked{$_} = 1 for @classnums;
+} else {
+ # default values
+ $classnum_ischecked{$_->classnum} = 1 for @active_classes;
+ $classnum_ischecked{0} = 1;
+}
+
+my @optin_checkboxes = (
[ 'message' => { label => 'Message recipients' } ],
[ 'invoice' => { label => 'Invoice recipients' } ],
);
-
-foreach my $class (qsearch('contact_class', { disabled => '' })) {
- push @contact_checkboxes, [
- $class->classnum,
- { label => $class->classname }
- ];
-}
+my @classnum_checkboxes = (
+ [ '0' => { label => '(None)' }],
+ map { [ $_->classnum => {label => $_->classname} ] } @active_classes,
+);
</%init>
diff --git a/httemplate/search/contact.html b/httemplate/search/contact.html
index 5f02fef2f..9abbcfa1d 100644
--- a/httemplate/search/contact.html
+++ b/httemplate/search/contact.html
@@ -11,12 +11,19 @@
header => \@header,
fields => \@fields,
links => \@links,
+ html_init => $send_email_link,
&>
<%init>
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('List contacts');
+# Catch classnum values from multi-select box
+# A classnum of 0 indicates to include rows where classnum IS NULL
+$CGI::LIST_CONTEXT_WARN = 0;
+my @classnum = grep{ /^\d+$/ && $_ > 0 } $cgi->param('classnum');
+my $classnum_null = grep{ $_ eq 0 } $cgi->param('classnum');
+
my @select = 'contact.contactnum AS contact_contactnum'; #if we select it as bare contactnum, the multi-customer listings go away
push @select, map "contact.$_", qw( first last title );
my %hash = ();
@@ -53,6 +60,18 @@ my $home_phone_sub = sub {
join(', ', map $_->phonenum, @contact_homephone);
};
+my $invoice_dest_sub = sub {
+ my $contact = shift;
+ my $cust_contact = qsearchs(cust_contact => {custnum => $contact->custnum});
+ $cust_contact->invoice_dest ? 'Y' : 'N';
+};
+
+my $message_dest_sub = sub {
+ my $contact = shift;
+ my $cust_contact = qsearchs(cust_contact => {custnum => $contact->custnum});
+ $cust_contact->message_dest ? 'Y' : 'N';
+};
+
my $link; #for closure in this sub, we'll define it later
my $contact_classname_sub = sub {
my $contact = shift;
@@ -63,14 +82,14 @@ my $contact_classname_sub = sub {
} elsif ( $link eq 'prospect_main' ) {
$X_contact = qsearchs('prospect_contact', { %hash, 'prospectnum' => $contact->prospectnum } );
} else {
- die 'guru meditation #5555';
+ die "guru meditation #5555 (\$link: $link)";
}
$X_contact->contact_classname;
};
-my @header = ( 'First', 'Last', 'Title', 'Email', 'Work Phone', 'Mobile Phone', 'Home Phone', 'Type' );
-my @fields = ( 'first', 'last', 'title', $email_sub, $work_phone_sub, $mobile_phone_sub, $home_phone_sub, $contact_classname_sub );
-my @links = ( '', '', '', '', '', '', '', '', );
+my @header = ( 'First', 'Last', 'Title', 'Email', 'Work Phone', 'Mobile Phone', 'Home Phone', 'Type', 'Invoice Destination', 'Message Destination');
+my @fields = ( 'first', 'last', 'title', $email_sub, $work_phone_sub, $mobile_phone_sub, $home_phone_sub, $contact_classname_sub, $invoice_dest_sub, $message_dest_sub );
+my @links = ( '', '', '', '', '', '', '', '', '', '');
my $company_link = '';
@@ -93,6 +112,14 @@ if ( $link ) {
' LEFT JOIN cust_contact USING ( contactnum ) '.
' LEFT JOIN cust_main ON ( cust_contact.custnum = cust_main.custnum )';
$extra_sql = ' cust_contact.custnum IS NOT NULL ';
+ if (@classnum || $classnum_null) {
+ $extra_sql .= ' AND ( ';
+ $extra_sql .= ' cust_contact.classnum IN ('.join(',', at classnum).') '
+ if @classnum;
+ $extra_sql .= ' OR ' if $classnum_null && @classnum;
+ $extra_sql .= ' cust_contact.classnum IS NULL ' if $classnum_null;
+ $extra_sql .= ' ) ';
+ }
$company_link = [ $p.'view/cust_main.cgi?', 'custnum' ];
} elsif ( $link eq 'prospect_main' ) {
push @header, 'Prospect';
@@ -103,6 +130,14 @@ if ( $link ) {
' LEFT JOIN prospect_contact USING ( contactnum ) '.
' LEFT JOIN prospect_main ON ( prospect_contact.prospectnum = prospect_main.prospectnum )';
$extra_sql = ' prospect_contact.prospectnum IS NOT NULL ';
+ if (@classnum || $classnum_null) {
+ $extra_sql .= ' AND ( ';
+ $extra_sql .= ' prospect_contact.classnum IN ('.join(',', at classnum).') '
+ if @classnum;
+ $extra_sql .= ' OR ' if $classnum_null && @classnum;
+ $extra_sql .= ' prospect_contact.classnum IS NULL ' if $classnum_null;
+ $extra_sql .= ' ) ';
+ }
$company_link = [ $p.'view/prospect_main.html?', 'prospectnum' ];
} else {
die "don't know how to report on contacts linked to specified table";
@@ -123,4 +158,21 @@ push @fields, 'comment';
$extra_sql = (keys(%hash) ? ' AND ' : ' WHERE '). $extra_sql
if $extra_sql;
+my $classnum_url_part;
+if (@classnum) {
+ $classnum_url_part = join '', map{ "&classnums=$_" } @classnum;
+ $classnum_url_part .= '&classnums=0' if $classnum_null;
+}
+my $send_email_link =
+ "<a href=\"${fsurl}misc/email-customers.html?".
+ 'table=cust_main'.
+ '&POST=on'.
+ '&all_pkg_classnums=0'.
+ '&all_tags=0'.
+ '&any_pkg_status=0'.
+ '&refnum=1'.
+ '&with_email=on'.
+ $classnum_url_part.
+ "\">Email a notice to these customers</a>";
+
</%init>
diff --git a/httemplate/search/report_contact.html b/httemplate/search/report_contact.html
index 3583bb428..ba91b4e7e 100644
--- a/httemplate/search/report_contact.html
+++ b/httemplate/search/report_contact.html
@@ -21,6 +21,11 @@
'curr_value' => scalar( $cgi->param('link') ),
&>
+ <& /elements/tr-select-multiple-contact_class.html,
+ label => 'Contact Type',
+ field => 'classnum',
+ &>
+
</FORM>
</TABLE>
-----------------------------------------------------------------------
Summary of changes:
FS/FS/cust_main.pm | 19 +++---
FS/FS/cust_main/Search.pm | 48 ++++++++++++--
.../elements/select-multiple-contact_class.html | 21 ++++++
.../elements/tr-select-multiple-contact_class.html | 32 +++++++++
httemplate/misc/email-customers.html | 75 +++++++++++++++++++---
httemplate/search/contact.html | 60 +++++++++++++++--
httemplate/search/report_contact.html | 5 ++
7 files changed, 233 insertions(+), 27 deletions(-)
create mode 100644 httemplate/elements/select-multiple-contact_class.html
create mode 100644 httemplate/elements/tr-select-multiple-contact_class.html
More information about the freeside-commits
mailing list