[freeside-commits] branch master updated. b2fd002f3285b70311642f8ff0025598d42bd16e

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


The branch, master has been updated
       via  b2fd002f3285b70311642f8ff0025598d42bd16e (commit)
      from  1daa37e733b9e972e5328503374130a423d02836 (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 b2fd002f3285b70311642f8ff0025598d42bd16e
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 5a2a9be..c1ed79c 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 f706722..3111f43 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