[freeside-commits] branch FREESIDE_4_BRANCH updated. ded0cb9e764cd806a13aaf6ddb8cdfe0f3215999

Mark Wells mark at 420.am
Wed Nov 18 13:12:46 PST 2015


The branch, FREESIDE_4_BRANCH has been updated
       via  ded0cb9e764cd806a13aaf6ddb8cdfe0f3215999 (commit)
      from  f4ebbb57fd21146b936e73fbcf34b76adf664904 (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 ded0cb9e764cd806a13aaf6ddb8cdfe0f3215999
Author: Mark Wells <mark at freeside.biz>
Date:   Wed Nov 18 13:07:47 2015 -0800

    track customer invoice destination emails using contact_email, #25536

diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 9f2cb76..4918e79 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1657,6 +1657,7 @@ sub tables_hashref {
         'po_number', 'varchar', 'NULL', $char_d, '', '',
         'invoice_attn', 'varchar', 'NULL', $char_d, '', '',
         'invoice_ship_address', 'char', 'NULL', 1, '', '',
+        'postal_invoice', 'char', 'NULL', 1, '', '',
       ],
       'primary_key'  => 'custnum',
       'unique'       => [ [ 'agentnum', 'agent_custid' ] ],
@@ -1812,6 +1813,7 @@ sub tables_hashref {
         '_password',          'varchar', 'NULL', $char_d, '', '',
         '_password_encoding', 'varchar', 'NULL', $char_d, '', '',
         'disabled',              'char', 'NULL',       1, '', '', 
+        'invoice_dest',          'char', 'NULL',       1, '', '',
       ],
       'primary_key'  => 'contactnum',
       'unique'       => [],
diff --git a/FS/FS/contact.pm b/FS/FS/contact.pm
index 6120480..0428d89 100644
--- a/FS/FS/contact.pm
+++ b/FS/FS/contact.pm
@@ -6,6 +6,7 @@ use vars qw( $skip_fuzzyfiles );
 use Carp;
 use Scalar::Util qw( blessed );
 use FS::Record qw( qsearch qsearchs dbh );
+use FS::Cursor;
 use FS::contact_phone;
 use FS::contact_email;
 use FS::queue;
@@ -88,6 +89,9 @@ empty or bcrypt
 
 disabled
 
+=item invoice_dest
+
+empty, or 'Y' if email invoices should be sent to this contact
 
 =back
 
@@ -111,6 +115,25 @@ sub table { 'contact'; }
 Adds this record to the database.  If there is an error, returns the error,
 otherwise returns false.
 
+If the object has an C<emailaddress> field, L<FS::contact_email> records will
+be created for each (comma-separated) email address in that field. If any of
+these coincide with an existing email address, this contact will be merged with
+the contact with that address.
+
+Then, if the object has any fields named C<phonetypenumN> an
+L<FS::contact_phone> record will be created for each of them. Those fields
+should contain phone numbers of the appropriate types (where N is the key of
+an L<FS::phone_type> record identifying the type of number: daytime, night,
+etc.).
+
+After inserting the record, if the object has a 'custnum' or 'prospectnum'
+field, an L<FS::cust_contact> or L<FS::prospect_contact> record will be
+created to link the contact to the customer. The following fields will also
+be included in that record, if they are set on the object:
+- classnum
+- comment
+- selfservice_access
+
 =cut
 
 sub insert {
@@ -643,6 +666,7 @@ sub check {
     || $self->ut_textn('_password')
     || $self->ut_enum('_password_encoding', [ '', 'bcrypt'])
     || $self->ut_enum('disabled', [ '', 'Y' ])
+    || $self->ut_flag('invoice_dest')
   ;
   return $error if $error;
 
@@ -886,6 +910,7 @@ sub cgi_contact_fields {
 
   my @contact_fields = qw(
     classnum first last title comment emailaddress selfservice_access
+    invoice_dest
   );
 
   push @contact_fields, 'phonetypenum'. $_->phonetypenum
@@ -899,6 +924,32 @@ use FS::upgrade_journal;
 sub _upgrade_data { #class method
   my ($class, %opts) = @_;
 
+  # always migrate cust_main_invoice records over
+  local $FS::cust_main::import = 1; # override require_phone and such
+  my $search = FS::Cursor->new('cust_main_invoice', {});
+  while (my $cust_main_invoice = $search->fetch) {
+    my $custnum = $cust_main_invoice->custnum;
+    my $dest = $cust_main_invoice->dest;
+    my $cust_main = $cust_main_invoice->cust_main;
+
+    if ( $dest =~ /^\d+$/ ) {
+      my $svc_acct = FS::svc_acct->by_key($dest);
+      die "custnum $custnum, invoice destination svcnum $svc_acct does not exist\n"
+        if !$svc_acct;
+      $dest = $svc_acct->email;
+    }
+
+    my $error = $cust_main->replace( [ $dest ] );
+
+    if ( $error ) {
+      die "custnum $custnum, invoice destination $dest, creating contact: $error\n";
+    }
+
+    $error = $cust_main_invoice->delete;
+    die "custnum $custnum, cleaning up cust_main_invoice: $error\n" if $error;
+
+  } # while $search->fetch
+
   unless ( FS::upgrade_journal->is_done('contact__DUPEMAIL') ) {
 
     foreach my $contact (qsearch('contact', {})) {
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 1f64b9e..4c09d8c 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -325,14 +325,7 @@ a better explanation of this, but until then, here's an example:
   );
   $cust_main->insert( \%hash );
 
-INVOICING_LIST_ARYREF: If you pass an arrarref to the insert method, it will
-be set as the invoicing list (see L<"invoicing_list">).  Errors return as
-expected and rollback the entire transaction; it is not necessary to call 
-check_invoicing_list first.  The invoicing_list is set after the records in the
-CUST_PKG_HASHREF above are inserted, so it is now possible to set an
-invoicing_list destination to the newly-created svc_acct.  Here's an example:
-
-  $cust_main->insert( {}, [ $email, 'POST' ] );
+INVOICING_LIST_ARYREF: No longer supported.
 
 Currently available options are: I<depend_jobnum>, I<noexport>,
 I<tax_exemption>, I<prospectnum>, I<contact> and I<contact_params>.
@@ -352,8 +345,8 @@ created and inserted.
 
 If I<prospectnum> is set, moves contacts and locations from that prospect.
 
-If I<contact> is set to an arrayref of FS::contact objects, inserts those
-new contacts with this new customer.
+If I<contact> is set to an arrayref of FS::contact objects, those will be
+inserted.
 
 If I<contact_params> is set to a hashref of CGI parameters (and I<contact> is
 unset), inserts those new contacts with this new customer.  Handles CGI
@@ -368,7 +361,10 @@ for an "m2" multiple entry field as passed by edit/cust_main.cgi
 sub insert {
   my $self = shift;
   my $cust_pkgs = @_ ? shift : {};
-  my $invoicing_list = @_ ? shift : '';
+  my $invoicing_list = $_[0];
+  if ( $invoicing_list and ref($invoicing_list) eq 'ARRAY' ) {
+    shift;
+  }
   my %options = @_;
   warn "$me insert called with options ".
        join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
@@ -495,19 +491,6 @@ sub insert {
     }
   }
 
-  warn "  setting invoicing list\n"
-    if $DEBUG > 1;
-
-  if ( $invoicing_list ) {
-    $error = $self->check_invoicing_list( $invoicing_list );
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      #return "checking invoicing_list (transaction rolled back): $error";
-      return $error;
-    }
-    $self->invoicing_list( $invoicing_list );
-  }
-
   warn "  setting customer tags\n"
     if $DEBUG > 1;
 
@@ -595,6 +578,27 @@ sub insert {
       return $error;
     }
   }
+  
+  if ( $invoicing_list ) {
+    warn "FS::cust_main::insert setting invoice destinations via invoicing_list\n"
+      if $DEBUG;
+
+    # okay, for now we'll still allow setting the contact this way
+    $invoicing_list = join(',', @$invoicing_list) if ref $invoicing_list;
+    my $contact = FS::contact->new({
+      'custnum'       => $self->get('custnum'),
+      'last'          => $self->get('last'),
+      'first'         => $self->get('first'),
+      'emailaddress'  => $invoicing_list,
+      'invoice_dest'  => 'Y',
+    });
+    my $error = $contact->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+
+  }
 
   warn "  setting cust_payby\n"
     if $DEBUG > 1;
@@ -1274,12 +1278,9 @@ To change the customer's address, set the pseudo-fields C<bill_location> and
 C<ship_location>.  The address will still only change if at least one of the
 address fields differs from the existing values.
 
-INVOICING_LIST_ARYREF: If you pass an arrarref to the insert method, it will
-be set as the invoicing list (see L<"invoicing_list">).  Errors return as
-expected and rollback the entire transaction; it is not necessary to call 
-check_invoicing_list first.  Here's an example:
-
-  $new_cust_main->replace( $old_cust_main, [ $email, 'POST' ] );
+INVOICING_LIST_ARYREF: If you pass an arrayref to this method, it will be
+set as the contact email address for a default contact with the same name as
+the customer.
 
 Currently available options are: I<tax_exemption>.
 
@@ -1347,6 +1348,45 @@ sub replace {
     $self->set($l.'num', $new_loc->locationnum);
   } #for $l
 
+  if ( @param && ref($param[0]) eq 'ARRAY' ) { # INVOICING_LIST_ARYREF
+    my $invoicing_list = shift @param;
+    my $email = '';
+    foreach (@$invoicing_list) {
+      if ($_ eq 'POST') {
+        $self->set('postal_invoice', 'Y');
+      } else {
+        $email .= ',' if length($email);
+        $email .= $_;
+      }
+    }
+    my @contacts = map { $_->contact } $self->cust_contact;
+    # if possible, use a contact that matches the customer's name
+    my ($contact) = grep { $_->first eq $old->get('first') and
+                           $_->last  eq $old->get('last') }
+                    @contacts;
+    $contact ||= FS::contact->new({
+        'custnum'       => $self->custnum,
+        'locationnum'   => $self->get('bill_locationnum'),
+    });
+    $contact->set('last', $self->get('last'));
+    $contact->set('first', $self->get('first'));
+    $contact->set('emailaddress', $email);
+    $contact->set('invoice_dest', 'Y');
+
+    my $error;
+    if ( $contact->contactnum ) {
+      $error = $contact->replace;
+    } elsif ( length($email) ) { # don't create a new contact if email is empty
+      $error = $contact->insert;
+    }
+
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+
+  }
+
   # replace the customer record
   my $error = $self->SUPER::replace($old);
 
@@ -1376,16 +1416,6 @@ sub replace {
     }
   }
 
-  if ( @param && ref($param[0]) eq 'ARRAY' ) { # INVOICING_LIST_ARYREF
-    my $invoicing_list = shift @param;
-    $error = $self->check_invoicing_list( $invoicing_list );
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return $error;
-    }
-    $self->invoicing_list( $invoicing_list );
-  }
-
   if ( $self->exists('tagnum') ) { #so we don't delete these on edit by accident
 
     #this could be more efficient than deleting and re-inserting, if it matters
@@ -1605,6 +1635,7 @@ sub check {
     || $self->ut_alphan('po_number')
     || $self->ut_enum('complimentary', [ '', 'Y' ])
     || $self->ut_flag('invoice_ship_address')
+    || $self->ut_flag('invoice_dest')
   ;
 
   foreach (qw(company ship_company)) {
@@ -2814,18 +2845,10 @@ sub tax_exemption {
 
 =item cust_main_exemption
 
-=item invoicing_list [ ARRAYREF ]
-
-If an arguement is given, sets these email addresses as invoice recipients
-(see L<FS::cust_main_invoice>).  Errors are not fatal and are not reported
-(except as warnings), so use check_invoicing_list first.
-
-Returns a list of email addresses (with svcnum entries expanded).
+=item invoicing_list
 
-Note: You can clear the invoicing list by passing an empty ARRAYREF.  You can
-check it without disturbing anything by passing nothing.
-
-This interface may change in the future.
+Returns a list of email addresses (with svcnum entries expanded), and the word
+'POST' if the customer receives postal invoices.
 
 =cut
 
@@ -2833,47 +2856,13 @@ sub invoicing_list {
   my( $self, $arrayref ) = @_;
 
   if ( $arrayref ) {
-    my @cust_main_invoice;
-    if ( $self->custnum ) {
-      @cust_main_invoice = 
-        qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
-    } else {
-      @cust_main_invoice = ();
-    }
-    foreach my $cust_main_invoice ( @cust_main_invoice ) {
-      #warn $cust_main_invoice->destnum;
-      unless ( grep { $cust_main_invoice->address eq $_ } @{$arrayref} ) {
-        #warn $cust_main_invoice->destnum;
-        my $error = $cust_main_invoice->delete;
-        warn $error if $error;
-      }
-    }
-    if ( $self->custnum ) {
-      @cust_main_invoice = 
-        qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
-    } else {
-      @cust_main_invoice = ();
-    }
-    my %seen = map { $_->address => 1 } @cust_main_invoice;
-    foreach my $address ( @{$arrayref} ) {
-      next if exists $seen{$address} && $seen{$address};
-      $seen{$address} = 1;
-      my $cust_main_invoice = new FS::cust_main_invoice ( {
-        'custnum' => $self->custnum,
-        'dest'    => $address,
-      } );
-      my $error = $cust_main_invoice->insert;
-      warn $error if $error;
-    }
+    warn "FS::cust_main::invoicing_list(ARRAY) is no longer supported.";
   }
   
-  if ( $self->custnum ) {
-    map { $_->address }
-      qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
-  } else {
-    ();
-  }
+  my @emails = $self->invoicing_list_emailonly;
+  push @emails, 'POST' if $self->get('postal_invoice');
 
+  @emails;
 }
 
 =item check_invoicing_list ARRAYREF
@@ -2911,18 +2900,6 @@ sub check_invoicing_list {
   '';
 }
 
-=item set_default_invoicing_list
-
-Sets the invoicing list to all accounts associated with this customer,
-overwriting any previous invoicing list.
-
-=cut
-
-sub set_default_invoicing_list {
-  my $self = shift;
-  $self->invoicing_list($self->all_emails);
-}
-
 =item all_emails
 
 Returns the email addresses of all accounts provisioned for this customer.
@@ -2952,10 +2929,11 @@ to receive postal invoices, does nothing.
 
 sub invoicing_list_addpost {
   my $self = shift;
-  return if grep { $_ eq 'POST' } $self->invoicing_list;
-  my @invoicing_list = $self->invoicing_list;
-  push @invoicing_list, 'POST';
-  $self->invoicing_list(\@invoicing_list);
+  if ( $self->get('postal_invoice') eq '' ) {
+    $self->set('postal_invoice', 'Y');
+    my $error = $self->replace;
+    warn $error if $error; # should fail harder, but this is traditional
+  }
 }
 
 =item invoicing_list_emailonly
@@ -2969,7 +2947,16 @@ sub invoicing_list_emailonly {
   my $self = shift;
   warn "$me invoicing_list_emailonly called"
     if $DEBUG;
-  grep { $_ !~ /^([A-Z]+)$/ } $self->invoicing_list;
+  return () if !$self->custnum; # not yet inserted
+  return map { $_->emailaddress }
+    qsearch({
+        table     => 'cust_contact',
+        select    => 'emailaddress',
+        addl_from => ' JOIN contact USING (contactnum) '.
+                     ' JOIN contact_email USING (contactnum)',
+        hashref   => { 'custnum' => $self->custnum, },
+        extra_sql => q( AND invoice_dest = 'Y'),
+    });
 }
 
 =item invoicing_list_emailonly_scalar
diff --git a/FS/FS/cust_main/Search.pm b/FS/FS/cust_main/Search.pm
index 097f2fb..c8a084c 100644
--- a/FS/FS/cust_main/Search.pm
+++ b/FS/FS/cust_main/Search.pm
@@ -531,10 +531,12 @@ sub email_search {
       if $DEBUG;
 
     push @cust_main,
-      map $_->cust_main,
+      map { $_->cust_main }
+      map { $_->cust_contact }
+      map { $_->contact }
           qsearch( {
-                     'table'     => 'cust_main_invoice',
-                     'hashref'   => { 'dest' => $email },
+                     'table'     => 'contact_email',
+                     'hashref'   => { 'emailaddress' => $email },
                    }
                  );
 
@@ -808,30 +810,24 @@ sub search {
   ##
 
   push @where,
-    'EXISTS ( SELECT 1 FROM cust_main_invoice
-                WHERE cust_main_invoice.custnum = cust_main.custnum
-                  AND length(dest) > 5
-            )'  # AND dest LIKE '%@%'
+    'EXISTS ( SELECT 1 FROM contact_email
+                JOIN cust_contact USING (contactnum)
+                WHERE cust_contact.custnum = cust_main.custnum
+            )'
     if $params->{'with_email'};
 
   ##
   # "with postal mail invoices" checkbox
   ##
 
-  push @where,
-    "EXISTS ( SELECT 1 FROM cust_main_invoice
-                WHERE cust_main_invoice.custnum = cust_main.custnum
-                  AND dest = 'POST' )"
+  push @where, "cust_main.postal_invoice = 'Y'"
     if $params->{'POST'};
 
   ##
   # "without postal mail invoices" checkbox
   ##
 
-  push @where,
-    "NOT EXISTS ( SELECT 1 FROM cust_main_invoice
-                    WHERE cust_main_invoice.custnum = cust_main.custnum
-                      AND dest = 'POST' )"
+  push @where, "cust_main.postal_invoice IS NULL"
     if $params->{'no_POST'};
 
   ##
diff --git a/FS/FS/cust_main_invoice.pm b/FS/FS/cust_main_invoice.pm
index b6ef260..6c155ac 100644
--- a/FS/FS/cust_main_invoice.pm
+++ b/FS/FS/cust_main_invoice.pm
@@ -11,6 +11,11 @@ use FS::Msgcat qw(gettext);
 
 FS::cust_main_invoice - Object methods for cust_main_invoice records
 
+=head1 ANNOUNCEMENT
+
+This is deprecated in version 4. Instead, contacts with the "invoice_dest"
+attribute should be used.
+
 =head1 SYNOPSIS
 
   use FS::cust_main_invoice;
diff --git a/FS/FS/part_event/Condition/nopostal.pm b/FS/FS/part_event/Condition/nopostal.pm
index b95cd5c..ea55ba5 100644
--- a/FS/FS/part_event/Condition/nopostal.pm
+++ b/FS/FS/part_event/Condition/nopostal.pm
@@ -10,17 +10,13 @@ sub condition {
   my( $self, $object ) = @_;
   my $cust_main = $self->cust_main($object);
 
-  scalar( grep { $_ eq 'POST' } $cust_main->invoicing_list ) ? 0 : 1;
+  $cust_main->postal_invoice eq '';
 }
 
 sub condition_sql {
   my( $self, $table ) = @_;
 
-  " NOT EXISTS( SELECT 1 FROM cust_main_invoice
-              WHERE cust_main_invoice.custnum = cust_main.custnum
-                AND cust_main_invoice.dest    = 'POST'
-          )
-  ";
+  " cust_main.postal_invoice IS NULL ";
 }
 
 1;
diff --git a/FS/FS/part_event/Condition/postal.pm b/FS/FS/part_event/Condition/postal.pm
index d0bd419..1dbe054 100644
--- a/FS/FS/part_event/Condition/postal.pm
+++ b/FS/FS/part_event/Condition/postal.pm
@@ -10,17 +10,13 @@ sub condition {
   my( $self, $object ) = @_;
   my $cust_main = $self->cust_main($object);
 
-  scalar( grep { $_ eq 'POST' } $cust_main->invoicing_list );
+  $cust_main->postal_invoice eq 'Y';
 }
 
 sub condition_sql {
   my( $self, $table ) = @_;
 
-  " EXISTS( SELECT 1 FROM cust_main_invoice
-              WHERE cust_main_invoice.custnum = cust_main.custnum
-                AND cust_main_invoice.dest    = 'POST'
-          )
-  ";
+  " cust_main.postal_invoice = 'Y' "
 }
 
 1;
diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm
index d3e23f2..9323976 100644
--- a/FS/FS/svc_acct.pm
+++ b/FS/FS/svc_acct.pm
@@ -44,7 +44,6 @@ use FS::PagedSearch qw( psearch ); # XXX in v4, replace with FS::Cursor
 use FS::part_pkg;
 use FS::part_svc;
 use FS::svc_acct_pop;
-use FS::cust_main_invoice;
 use FS::svc_domain;
 use FS::svc_pbx;
 use FS::raddb;
@@ -711,9 +710,37 @@ sub insert {
         || $conf->exists('emailinvoiceauto')
         && ! $cust_main->invoicing_list_emailonly
        ) {
-      my @invoicing_list = $cust_main->invoicing_list;
-      push @invoicing_list, $self->email;
-      $cust_main->invoicing_list(\@invoicing_list);
+
+      # slight false laziness w/ edit/process/cust_main.cgi...
+      # and also slightly arbitrary behavior.
+      # if the "real name" of this account matches the first + last name
+      # of a contact, attach the email address to that person.
+      my @contacts = map { $_->contact } $cust_main->cust_contact;
+      my $myname = $self->get('finger');
+      my ($contact) =
+        grep { $_->get('first') . ' ' . $_->get('last') eq $myname } @contacts;
+      # otherwise just pick the first one
+      $contact ||= $contacts[0];
+      # if there is one
+      $contact ||= FS::contact->new({
+          'custnum'       => $cust_main->get('custnum'),
+          'locationnum'   => $cust_main->get('bill_locationnum'),
+          'last'          => $cust_main->get('last'),
+          'first'         => $cust_main->get('first'),
+      });
+      $contact->set('emailaddress', $self->email);
+      $contact->set('invoice_dest', 'Y');
+
+      if ( $contact->get('contactnum') ) {
+        $error = $contact->replace;
+      } else {
+        $error = $contact->insert;
+      }
+
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "creating invoice destination contact: $error";
+      }
     }
 
     #welcome email
@@ -800,23 +827,6 @@ sub delete {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
-  foreach my $cust_main_invoice (
-    qsearch( 'cust_main_invoice', { 'dest' => $self->svcnum } )
-  ) {
-    unless ( defined($cust_main_invoice) ) {
-      warn "WARNING: something's wrong with qsearch";
-      next;
-    }
-    my %hash = $cust_main_invoice->hash;
-    $hash{'dest'} = $self->email;
-    my $new = new FS::cust_main_invoice \%hash;
-    my $error = $new->replace($cust_main_invoice);
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return $error;
-    }
-  }
-
   foreach my $svc_domain (
     qsearch( 'svc_domain', { 'catchall' => $self->svcnum } )
   ) {
diff --git a/httemplate/REST/1.0/cust_main b/httemplate/REST/1.0/cust_main
index 4656bcb..5401195 100644
--- a/httemplate/REST/1.0/cust_main
+++ b/httemplate/REST/1.0/cust_main
@@ -47,17 +47,23 @@ if ( $r->method eq 'GET' ) {
     if ( $cgi->param('cust_main_invoice_dest') ) {
       my $dest = dbh->quote(scalar($cgi->param('cust_main_invoice_dest')));
       $extra_sql = "
-        WHERE EXISTS ( SELECT 1 FROM cust_main_invoice
-                         WHERE cust_main.custnum = cust_main_invoice.custnum
-                           AND dest = $dest
+        WHERE EXISTS ( SELECT 1 FROM cust_contact
+                         JOIN contact USING (contactnum)
+                         JOIN contact_email USING (contactnum)
+                         WHERE cust_main.custnum = cust_contact.custnum
+                           AND contact.invoice_dest = 'Y'
+                           AND contact_email.emailaddress = $dest
                      )
       ";
     } elsif ( $cgi->param('cust_main_invoice_dest_substring') ) {
       my $dest = dbh->quote('%'. scalar($cgi->param('cust_main_invoice_dest_substring')). '%');
       $extra_sql = "
-        WHERE EXISTS ( SELECT 1 FROM cust_main_invoice
-                         WHERE cust_main.custnum = cust_main_invoice.custnum
-                           AND dest ILIKE $dest
+        WHERE EXISTS ( SELECT 1 FROM cust_contact
+                         JOIN contact USING (contactnum)
+                         JOIN contact_email USING (contactnum)
+                         WHERE cust_main.custnum = cust_contact.custnum
+                           AND contact.invoice_dest = 'Y'
+                           AND contact_email.emailaddress ILIKE $dest
                      )
       ";
     }
diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
index effe84b..bdf3431 100755
--- a/httemplate/edit/cust_main.cgi
+++ b/httemplate/edit/cust_main.cgi
@@ -294,8 +294,7 @@ if ( $cgi->param('error') ) {
   $cust_main->agentnum( $conf->config('default_agentnum') )
     if $conf->exists('default_agentnum');
   $cust_main->referral_custnum( $cgi->param('referral_custnum') );
-  @invoicing_list = ();
-  push @invoicing_list, 'POST'
+  $cust_main->set('postal_invoice', 'Y')
     unless $conf->exists('disablepostalinvoicedefault');
   $ss = '';
   $stateid = '';
diff --git a/httemplate/edit/cust_main/basics.html b/httemplate/edit/cust_main/basics.html
index 32a03bb..c3768ac 100644
--- a/httemplate/edit/cust_main/basics.html
+++ b/httemplate/edit/cust_main/basics.html
@@ -31,6 +31,8 @@
       $('#spouse_label').slideUp();
       $('#spouse_last_input').slideUp();
       $('#spouse_first_input').slideUp();
+      $('#invoice_email_label').slideUp();
+      $('#invoice_email_input').slideUp();
     } else {
       if ( document.getElementById('company').value.length == 0 ) {
         $('#company_label').slideUp();
@@ -40,6 +42,8 @@
       $('#spouse_label').slideDown();
       $('#spouse_last_input').slideDown();
       $('#spouse_first_input').slideDown();
+      $('#invoice_email_label').slideDown();
+      $('#invoice_email_input').slideDown();
     }
   }
 
diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html
index 6f716c1..7bca17b 100644
--- a/httemplate/edit/cust_main/billing.html
+++ b/httemplate/edit/cust_main/billing.html
@@ -25,9 +25,13 @@
 
 %   if ( $curuser->access_right('Complimentary customer') ) {
 
-      <TR>
-        <TD COLSPAN="2"><INPUT TYPE="checkbox" NAME="complimentary" VALUE="Y" <% $cust_main->complimentary eq "Y" ? 'CHECKED' : '' %>>Complimentary customer
-      </TR>
+    <& /elements/tr-checkbox.html,
+      field       => 'complimentary',
+      label       => emt('Complimentary customer'),
+      value       => 'Y',
+      curr_value  => $cust_main->complimentary,
+      box_first   => 1,
+    &>
 
 %   } else {
 
@@ -51,9 +55,13 @@
 
 %   } else {
 
-      <TR>
-        <TD COLSPAN="2"><INPUT TYPE="checkbox" NAME="tax" VALUE="Y" <% $cust_main->tax eq "Y" ? 'CHECKED' : '' %>> Tax Exempt<% @exempt_groups ? ' (all taxes)' : '' %></TD>
-      </TR>
+    <& /elements/tr-checkbox.html,
+      field       => 'tax',
+      label       => emt('Tax Exempt' . (scalar(@exempt_groups) ? '(all taxes)' : '') ),
+      value       => 'Y',
+      curr_value  => $cust_main->tax,
+      box_first   => 1,
+    &>
 
 %   }
 
@@ -66,7 +74,7 @@
           <TD STYLE="white-space:nowrap">  <INPUT TYPE="checkbox" NAME="tax_<% $exempt_group %>" ID="tax_<% $exempt_group %>" VALUE="Y" <% $checked ? 'CHECKED' : '' %> onChange="tax_changed(this)"> Tax Exempt (<% $exempt_group %> taxes)</TD>
           <TD> - Exemption number <INPUT TYPE="text" NAME="tax_<% $exempt_group %>_num" ID="tax_<% $exempt_group %>_num" VALUE="<% $cgi->param("tax_$exempt_group".'_num') || ( $cust_main_exemption ? $cust_main_exemption->exempt_number : '' ) |h %>" <% $checked ? '' : 'DISABLED' %>></TD>
         </TR>
-%     }
+%     } #"
 %   }
 
 %   ###
@@ -75,18 +83,13 @@
 
 % unless ( $conf->exists('emailinvoiceonly') ) {
 
-    <TR>
-      <TD COLSPAN="2"><INPUT TYPE="checkbox" NAME="invoicing_list_POST" VALUE="POST" <%
-
-        ( grep { $_ eq 'POST' } @invoicing_list )
-
-          ? 'CHECKED'
-          : ''
-
-        %>> <% mt('Postal mail invoices') |h %> 
-
-      </TD>
-    </TR>
+    <& /elements/tr-checkbox.html,
+      field       => 'postal_invoice',
+      label       => emt('Postal mail invoices'),
+      value       => 'Y',
+      curr_value  => $cust_main->postal_invoice,
+      box_first   => 1,
+    &>
 
 % }
 
@@ -94,33 +97,21 @@
 %   # email invoices
 %   ###
 
-    <TR>
-      <TD COLSPAN="2"><INPUT TYPE="checkbox" NAME="invoice_email" VALUE="Y" <%
-
-        ( $cust_main->invoice_noemail eq 'Y' )
-          ? ''
-          : 'CHECKED'
-
-        %>> <% mt('Email invoices') |h %> 
-
-      </TD>
-    </TR>
+    <& /elements/tr-checkbox.html,
+      field       => 'invoice_noemail',
+      label       => emt('Do not send email invoices'),
+      value       => 'Y',
+      curr_value  => $cust_main->invoice_noemail,
+      box_first   => 1,
+    &>
 
-% unless ( $conf->exists('cust-email-high-visibility')) {
-   <TR>
-      <TH ALIGN="right" WIDTH="200">
-        <% $conf->exists('cust_main-require_invoicing_list_email', $agentnum) 
-            ? $r : '' %>Email address(es)
-      </TD>
-      <TD WIDTH="408"><INPUT TYPE="text" NAME="invoicing_list" VALUE="<% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) %>">
-      <INPUT TYPE="checkbox" NAME="message_noemail" VALUE="Y" <%
-        ( $cust_main->message_noemail eq 'Y' )
-          ? 'CHECKED'
-          : ''
-        %>> <FONT SIZE="-1"><% emt('Do not send notices') %></FONT>
-      </TD>
-    </TR>
-% }
+    <& /elements/tr-checkbox.html,
+      field       => 'message_noemail',
+      label       => emt('Do not send other email notices'),
+      value       => 'Y',
+      curr_value  => $cust_main->message_noemail,
+      box_first   => 1,
+    &>
 
 %   ###
 %   # prorate_day
diff --git a/httemplate/edit/cust_main/name.html b/httemplate/edit/cust_main/name.html
index 13bd097..12d9d74 100644
--- a/httemplate/edit/cust_main/name.html
+++ b/httemplate/edit/cust_main/name.html
@@ -29,19 +29,18 @@
 </TR>
 % }
 
-% if ( $conf->exists('cust-email-high-visibility') ) {
 <TR>
-  <TH ALIGN="right" CLASS="
+  <TH ALIGN="right">
+    <SPAN ID="invoice_email_label" CLASS="
     <% $conf->exists('cust_main-require_invoicing_list_email', $agentnum)
         ? 'required label'
-        : 'label' %>">Email address(es)
-  </TD>
-  <TD BGCOLOR="#FFFF00">
-    <INPUT TYPE="text" NAME="invoicing_list" 
+        : 'label' %>">Email address(es)</SPAN>
+  </TH>
+  <TD>
+    <INPUT TYPE="text" NAME="invoice_email"  ID="invoice_email_input"
            VALUE="<% $cust_main->invoicing_list_emailonly_scalar %>">
   </TD>
 </TR>
-% }
 <%init>
 my $cust_main = shift;
 my $agentnum = $cust_main->agentnum if $cust_main->custnum;
diff --git a/httemplate/edit/process/cust_main-contacts.html b/httemplate/edit/process/cust_main-contacts.html
index 10ec363..2a7185b 100644
--- a/httemplate/edit/process/cust_main-contacts.html
+++ b/httemplate/edit/process/cust_main-contacts.html
@@ -3,6 +3,7 @@
      'error_redirect' => popurl(3). 'edit/cust_main-contacts.html?',
      'agent_virt'     => 1,
      'skip_process'   => 1, #we don't want to make any changes to cust_main
+     'precheck_callback' => $precheck_callback,
      'process_o2m' => {
        'table'  => 'contact',
        'fields' => FS::contact->cgi_contact_fields,
@@ -10,3 +11,22 @@
      'redirect' => popurl(3). 'view/cust_main.cgi?',
    )
 %>
+<%init>
+my $precheck_callback = sub {
+  my $cgi = shift;
+  my $conf = FS::Conf->new;
+  if ( $conf->exists('cust_main-require_invoicing_list_email') ) {
+    my $has_email = 0;
+    foreach my $prefix (grep /^contactnum\d+$/, $cgi->param) {
+      if ( length($cgi->param($prefix . '_emailaddress'))
+           and $cgi->param($prefix . '_invoice_dest') ) {
+        $has_email = 1;
+        last;
+      }
+    }
+    return "At least one contact must receive email invoices"
+      unless $has_email;
+  }
+  '';
+};
+</%init>
diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi
index 52a2608..a9f7cf4 100755
--- a/httemplate/edit/process/cust_main.cgi
+++ b/httemplate/edit/process/cust_main.cgi
@@ -29,10 +29,12 @@ $cgi->param('tax','') unless defined $cgi->param('tax');
 
 $cgi->param('refnum', (split(/:/, ($cgi->param('refnum'))[0] ))[0] );
 
-my @invoicing_list = split( /\s*\,\s*/, $cgi->param('invoicing_list') );
-push @invoicing_list, 'POST' if $cgi->param('invoicing_list_POST');
-push @invoicing_list, 'FAX' if $cgi->param('invoicing_list_FAX');
-$cgi->param('invoicing_list', join(',', @invoicing_list) );
+#my @invoicing_list = split( /\s*\,\s*/, $cgi->param('invoicing_list') );
+#push @invoicing_list, 'POST' if $cgi->param('invoicing_list_POST');
+#push @invoicing_list, 'FAX' if $cgi->param('invoicing_list_FAX');
+#$cgi->param('invoicing_list', join(',', @invoicing_list) );
+
+my $agentnum = $cgi->param('agentnum');
 
 # is this actually used?  if so, we need to clone locations...
 # but I can't find anything that sets this parameter to a non-empty value
@@ -78,7 +80,7 @@ my $new = new FS::cust_main ( {
   map { ( "ship_$_", '' ) } (FS::cust_main->location_fields)
 } );
 
-$new->invoice_noemail( ($cgi->param('invoice_email') eq 'Y') ? '' : 'Y' );
+warn Dumper( $new ) if $DEBUG > 1;
 
 if ( $duplicate_of ) {
   # then negate all changes to the customer; the only change we should
@@ -157,6 +159,36 @@ if ( $curuser->access_right('Edit customer tax exemptions') ) {
 $options{'contact_params'} = scalar($cgi->Vars);
 $options{'cust_payby_params'} = scalar($cgi->Vars);
 
+my $email;
+
+if ( $cgi->param('residential_commercial') eq 'Residential' ) {
+
+  $email = $cgi->param('invoice_email') || '';
+  if ( length($email) == 0 and $conf->exists('cust_main-require_invoicing_list_email', $agentnum) ) {
+    $error = 'Email address required';
+  }
+
+  # XXX really should include the phone numbers in here also
+
+} else {
+
+  # contact UI is enabled; everything will be passed through via
+  # contact_params
+  if ($conf->exists('cust_main-require_invoicing_list_email', $agentnum)) {
+    my $has_email = 0;
+    foreach my $prefix (grep /^contactnum\d+$/, $cgi->param) {
+      if ( length($cgi->param($prefix . '_emailaddress'))
+           and $cgi->param($prefix . '_invoice_dest') ) {
+        $has_email = 1;
+        last;
+      }
+    }
+    $error = "At least one contact must receive email invoices"
+      unless $has_email;
+  }
+
+}
+
 #perhaps this stuff should go to cust_main.pm
 if ( $new->custnum eq '' or $duplicate_of ) {
 
@@ -263,7 +295,8 @@ if ( $new->custnum eq '' or $duplicate_of ) {
   }
   else {
     # create the customer
-    $error ||= $new->insert( \%hash, \@invoicing_list,
+    $error ||= $new->insert( \%hash,
+                             [ $email ],
                              %options,
                              prospectnum => scalar($cgi->param('prospectnum')),
                            );
@@ -296,16 +329,14 @@ if ( $new->custnum eq '' or $duplicate_of ) {
     $new->signupdate($old->signupdate);
   }
 
-  warn "$me calling $new -> replace( $old, \ @invoicing_list )" if $DEBUG;
+  warn "$me calling $new -> replace( $old )" if $DEBUG;
   local($FS::cust_main::DEBUG) = $DEBUG if $DEBUG;
   local($FS::Record::DEBUG)    = $DEBUG if $DEBUG;
 
   local($Data::Dumper::Sortkeys) = 1;
   warn Dumper({ new => $new, old => $old }) if $DEBUG;
 
-  $error ||= $new->replace( $old, \@invoicing_list,
-                            %options,
-                          );
+  $error ||= $new->replace( $old, [ $email ], %options );
 
   warn "$me returned from replace" if $DEBUG;
   
diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html
index 87e15de..ab14dfb 100644
--- a/httemplate/elements/contact.html
+++ b/httemplate/elements/contact.html
@@ -59,13 +59,22 @@
 %               }
 %             }
             </SELECT>
-
+%         } elsif ( $field eq 'invoice_dest' ) {
+%           my $curr_value = $cgi->param($name . '_' . $field);
+%           $curr_value = $value if !defined($curr_value);
+            <& select.html,
+                field         => $name . '_' . $field,
+                curr_value    => $curr_value,
+                options       => [ '', 'Y' ],
+                option_labels => { '' => 'no', 'Y' => 'yes' },
+                style         => 'width: 100%',
+            &>
 %         } else {
             <INPUT TYPE  = "text"
                    NAME  = "<%$name%>_<%$field%>"
                    ID    = "<%$id%>_<%$field%>"
                    SIZE  = "<% $size{$field} || 14 %>"
-                   VALUE = "<% scalar($cgi->param($name."_$field"))
+                   VALUE = "<% scalar($cgi->param($name . '_' . $field))
                                || $value |h %>"
                    <% $onchange %>
             >
@@ -130,6 +139,7 @@ tie my %label, 'Tie::IxHash',
   'last'               => 'Last name',
   'title'              => 'Title/Position',
   'emailaddress'       => 'Email',
+  'invoice_dest'       => 'Send invoices',
   'selfservice_access' => 'Self-service'
 ;
 
diff --git a/httemplate/elements/tr-checkbox.html b/httemplate/elements/tr-checkbox.html
index 5761263..ed16650 100644
--- a/httemplate/elements/tr-checkbox.html
+++ b/httemplate/elements/tr-checkbox.html
@@ -9,13 +9,26 @@ Example:
   &>
 
 </%doc>
-<% include('tr-td-label.html', @_ ) %>
+% if ( $opt{'box_first'} ) {
+  <TR>
+    <TH COLSPAN="<% $opt{'colspan'} || 2 %>"
+      VALIGN = "<% $opt{'valign'} || 'top' %>"
+      STYLE  = "<% $style %>"
+      ID     = "<% $opt{label_id} || $opt{id}. '_label0' %>"
+    >
+      <& checkbox.html, @_ &>
+      <% $required %><% $opt{label} %>
+    </TH>
+  </TR>
+% } else {
+<& tr-td-label.html, @_ &>
 
   <TD <% $style %>>
     <% include('checkbox.html', @_) %>
   </TD>
 
 </TR>
+% }
 
 <%init>
 
@@ -25,6 +38,12 @@ my $onchange = $opt{'onchange'}
                  ? 'onChange="'. $opt{'onchange'}. '(this)"'
                  : '';
 
-my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+my $style = 'text-align: left; padding-top: 3px';
+$style .= '; '. $opt{'cell_style'} if $opt{'cell_style'};
+
+my $required = $opt{'required'} ? '<font color="#ff0000">*</font> ' : '';
+if ($required) {
+  $style .= ';font-weight: bold';
+}
 
 </%init>
diff --git a/httemplate/elements/tr-td-label.html b/httemplate/elements/tr-td-label.html
index 78690f1..3535c63 100644
--- a/httemplate/elements/tr-td-label.html
+++ b/httemplate/elements/tr-td-label.html
@@ -2,6 +2,9 @@
 
 Actually <TR> <TH> $label </TH>
 
+Note that this puts the 'label' argument into the document verbatim, with no
+escaping or localization.
+
 </%doc>
 <TR>
 
diff --git a/httemplate/misc/xmlhttp-cust_main-email_search.html b/httemplate/misc/xmlhttp-cust_main-email_search.html
index 0d83082..eb9ecc8 100644
--- a/httemplate/misc/xmlhttp-cust_main-email_search.html
+++ b/httemplate/misc/xmlhttp-cust_main-email_search.html
@@ -6,14 +6,14 @@ die 'access denied'
 my $sub = $cgi->param('sub');
 my $email = $cgi->param('arg');
 my @where = (
-  "cust_main_invoice.dest != 'POST'",
-  "cust_main_invoice.dest LIKE ".dbh->quote('%'.$email.'%'),
+  'contact_email.emailaddress LIKE '.dbh->quote('%'.$email.'%'),
   $FS::CurrentUser::CurrentUser->agentnums_sql(table => 'cust_main'),
 );
 my @cust_main = qsearch({
   'table'     => 'cust_main',
-  'select'    => 'cust_main.*, cust_main_invoice.dest',
-  'addl_from' => 'JOIN cust_main_invoice USING (custnum)',
+  'select'    => 'cust_main.*',
+  'addl_from' => ' JOIN cust_contact USING (custnum) '.
+                 ' JOIN contact_email USING (contactnum)',
   'extra_sql' => 'WHERE '.join(' AND ', @where),
 });
 
diff --git a/httemplate/view/cust_main/contacts_new.html b/httemplate/view/cust_main/contacts_new.html
index 590409d..d55ee3d 100644
--- a/httemplate/view/cust_main/contacts_new.html
+++ b/httemplate/view/cust_main/contacts_new.html
@@ -1,4 +1,4 @@
-%if ( @cust_contacts ) {
+% if ( $display and @cust_contacts ) {
 <BR>
 <FONT CLASS="fsinnerbox-title">Contacts</FONT>
 
@@ -9,6 +9,7 @@
   <%$th%>Type</TH>
   <%$th%>Contact</TH>
   <%$th%>Email</TH>
+  <%$th%>Send invoices</TH>
   <%$th%>Self-service</TH>
 % foreach my $phone_type (@phone_type) {
     <%$th%><% $phone_type->typename |h %></TH>
@@ -30,7 +31,7 @@
 
 %       my @contact_email = $contact->contact_email;
         <%$td%><% join(', ', map $_->emailaddress, @contact_email) %></TD>
-
+        <%$td%><% $contact->invoice_dest eq 'Y' ? 'Yes' : 'No' %></TD>
         <%$td%>
 %         if ( $cust_contact->selfservice_access ) {
             Enabled
@@ -75,4 +76,9 @@ my( $cust_main ) = @_;
 
 my @cust_contacts = $cust_main->cust_contact;
 
+# residential customers have a default "invisible" contact, but if they
+# somehow get more than one contact, show them
+my $display = (length($cust_main->residential_commercial) > 0)
+              or ( scalar(@cust_contacts) > 1 );
+
 </%init>

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

Summary of changes:
 FS/FS/Schema.pm                                    |    2 +
 FS/FS/contact.pm                                   |   51 +++++
 FS/FS/cust_main.pm                                 |  199 +++++++++-----------
 FS/FS/cust_main/Search.pm                          |   26 ++-
 FS/FS/cust_main_invoice.pm                         |    5 +
 FS/FS/part_event/Condition/nopostal.pm             |    8 +-
 FS/FS/part_event/Condition/postal.pm               |    8 +-
 FS/FS/svc_acct.pm                                  |   52 ++---
 httemplate/REST/1.0/cust_main                      |   18 +-
 httemplate/edit/cust_main.cgi                      |    3 +-
 httemplate/edit/cust_main/basics.html              |    4 +
 httemplate/edit/cust_main/billing.html             |   81 ++++----
 httemplate/edit/cust_main/name.html                |   13 +-
 httemplate/edit/process/cust_main-contacts.html    |   20 ++
 httemplate/edit/process/cust_main.cgi              |   51 ++++-
 httemplate/elements/contact.html                   |   14 +-
 httemplate/elements/tr-checkbox.html               |   23 ++-
 httemplate/elements/tr-td-label.html               |    3 +
 .../misc/xmlhttp-cust_main-email_search.html       |    8 +-
 httemplate/view/cust_main/contacts_new.html        |   10 +-
 20 files changed, 365 insertions(+), 234 deletions(-)




More information about the freeside-commits mailing list