add northern cyprus, RT#39335
[freeside.git] / FS / FS / cust_main.pm
index f0c4f5c..4fb4f52 100644 (file)
@@ -28,11 +28,10 @@ use Date::Format;
 #use Date::Manip;
 use File::Temp; #qw( tempfile );
 use Business::CreditCard 0.28;
-use Locale::Country;
 use FS::UID qw( dbh driver_name );
 use FS::Record qw( qsearchs qsearch dbdef regexp_sql );
 use FS::Cursor;
-use FS::Misc qw( generate_email send_email generate_ps do_print money_pretty );
+use FS::Misc qw( generate_ps do_print money_pretty );
 use FS::Msgcat qw(gettext);
 use FS::CurrentUser;
 use FS::TicketSystem;
@@ -325,14 +324,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 +344,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 +360,11 @@ 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;
+  if ( $_[0] and ref($_[0]) eq 'ARRAY' ) {
+    warn "cust_main::insert using deprecated invoicing list argument";
+    $invoicing_list = 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;
 
@@ -572,6 +555,25 @@ sub insert {
   warn "  setting contacts\n"
     if $DEBUG > 1;
 
+  $invoicing_list ||= $options{'invoicing_list'};
+  if ( $invoicing_list ) {
+
+    $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;
+    }
+
+  }
+
   if ( my $contact = delete $options{'contact'} ) {
 
     foreach my $c ( @$contact ) {
@@ -599,7 +601,18 @@ sub insert {
   warn "  setting cust_payby\n"
     if $DEBUG > 1;
 
-  if ( my $cust_payby_params = delete $options{'cust_payby_params'} ) {
+  if ( $options{cust_payby} ) {
+
+    foreach my $cust_payby ( @{ $options{cust_payby} } ) {
+      $cust_payby->custnum($self->custnum);
+      my $error = $cust_payby->insert;
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return $error;
+      }
+    }
+
+  } elsif ( my $cust_payby_params = delete $options{'cust_payby_params'} ) {
 
     my $error = $self->process_o2m(
       'table'         => 'cust_payby',
@@ -1263,19 +1276,25 @@ 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>.
+Currently available options are: I<tax_exemption>, I<cust_payby_params>, 
+I<contact_params>, I<invoicing_list>.
 
 The I<tax_exemption> option can be set to an arrayref of tax names or a hashref
 of tax names and exemption numbers.  FS::cust_main_exemption records will be
 deleted and inserted as appropriate.
 
+I<cust_payby_params> and I<contact_params> can be hashrefs of named parameter
+groups (describing the customer's payment methods and contacts, respectively)
+in the style supported by L<FS::o2m_Common/process_o2m>. See L<FS::cust_payby>
+and L<FS::contact> for the fields these can contain.
+
+I<invoicing_list> is a synonym for the INVOICING_LIST_ARYREF parameter, and
+should be used instead if possible.
+
 =cut
 
 sub replace {
@@ -1336,6 +1355,54 @@ sub replace {
     $self->set($l.'num', $new_loc->locationnum);
   } #for $l
 
+  my $invoicing_list;
+  if ( @param && ref($param[0]) eq 'ARRAY' ) { # INVOICING_LIST_ARYREF
+    warn "cust_main::replace: using deprecated invoicing list argument";
+    $invoicing_list = shift @param;
+  }
+
+  my %options = @param;
+
+  $invoicing_list ||= $options{invoicing_list};
+
+  if ( $invoicing_list ) {
+    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);
 
@@ -1365,16 +1432,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
@@ -1397,8 +1454,6 @@ sub replace {
 
   }
 
-  my %options = @param;
-
   my $tax_exemption = delete $options{'tax_exemption'};
   if ( $tax_exemption ) {
 
@@ -1456,6 +1511,24 @@ sub replace {
 
   }
 
+  if ( my $contact_params = delete $options{'contact_params'} ) {
+
+    # this can potentially replace contacts that were created by the
+    # invoicing list argument, but the UI shouldn't allow both of them
+    # to be specified
+
+    my $error = $self->process_o2m(
+      'table'         => 'contact',
+      'fields'        => FS::contact->cgi_contact_fields,
+      'params'        => $contact_params,
+    );
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+
+  }
+
   unless ( $import || $skip_fuzzyfiles ) {
     $error = $self->queue_fuzzyfiles_update;
     if ( $error ) {
@@ -1594,6 +1667,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)) {
@@ -2803,18 +2877,10 @@ sub tax_exemption {
 
 =item cust_main_exemption
 
-=item invoicing_list [ ARRAYREF ]
+=item invoicing_list
 
-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).
-
-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
 
@@ -2822,47 +2888,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
@@ -2900,18 +2932,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.
@@ -2941,10 +2961,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
@@ -2958,7 +2979,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
@@ -3838,26 +3868,14 @@ sub ship_contact_firstlast {
   $contact->get('first') . ' '. $contact->get('last');
 }
 
-#XXX this doesn't work in 3.x+
-#=item country_full
-#
-#Returns this customer's full country name
-#
-#=cut
-#
-#sub country_full {
-#  my $self = shift;
-#  code2country($self->country);
-#}
-
 sub bill_country_full {
   my $self = shift;
-  code2country($self->bill_location->country);
+  $self->bill_location->country_full;
 }
 
 sub ship_country_full {
   my $self = shift;
-  code2country($self->ship_location->country);
+  $self->ship_location->country_full;
 }
 
 =item county_state_county [ PREFIX ]
@@ -4053,6 +4071,30 @@ sub tickets {
   (@tickets);
 }
 
+=item appointments [ STATUS ]
+
+Returns an array of hashes representing the customer's RT tickets which
+are appointments.
+
+=cut
+
+sub appointments {
+  my $self = shift;
+  my $status = ( @_ && $_[0] ) ? shift : '';
+
+  return () unless $conf->config('ticket_system');
+
+  my $queueid = $conf->config('ticket_system-appointment-queueid');
+
+  @{ FS::TicketSystem->customer_tickets( $self->custnum,
+                                         99,
+                                         undef,
+                                         $status,
+                                         $queueid,
+                                       )
+  };
+}
+
 # Return services representing svc_accts in customer support packages
 sub support_services {
   my $self = shift;
@@ -4574,102 +4616,102 @@ sub search {
 
 =over 4
 
-=item notify CUSTOMER_OBJECT TEMPLATE_NAME OPTIONS
-
-Deprecated.  Use event notification and message templates 
-(L<FS::msg_template>) instead.
-
-Sends a templated email notification to the customer (see L<Text::Template>).
-
-OPTIONS is a hash and may include
-
-I<from> - the email sender (default is invoice_from)
-
-I<to> - comma-separated scalar or arrayref of recipients 
-   (default is invoicing_list)
+#=item notify CUSTOMER_OBJECT TEMPLATE_NAME OPTIONS
 
-I<subject> - The subject line of the sent email notification
-   (default is "Notice from company_name")
+#Deprecated.  Use event notification and message templates 
+#(L<FS::msg_template>) instead.
 
-I<extra_fields> - a hashref of name/value pairs which will be substituted
-   into the template
+#Sends a templated email notification to the customer (see L<Text::Template>).
 
-The following variables are vavailable in the template.
+#OPTIONS is a hash and may include
 
-I<$first> - the customer first name
-I<$last> - the customer last name
-I<$company> - the customer company
-I<$payby> - a description of the method of payment for the customer
-            # would be nice to use FS::payby::shortname
-I<$payinfo> - the account information used to collect for this customer
-I<$expdate> - the expiration of the customer payment in seconds from epoch
+#I<from> - the email sender (default is invoice_from)
 
-=cut
+#I<to> - comma-separated scalar or arrayref of recipients 
+#   (default is invoicing_list)
 
-sub notify {
-  my ($self, $template, %options) = @_;
-
-  return unless $conf->exists($template);
-
-  my $from = $conf->invoice_from_full($self->agentnum)
-    if $conf->exists('invoice_from', $self->agentnum);
-  $from = $options{from} if exists($options{from});
-
-  my $to = join(',', $self->invoicing_list_emailonly);
-  $to = $options{to} if exists($options{to});
-  
-  my $subject = "Notice from " . $conf->config('company_name', $self->agentnum)
-    if $conf->exists('company_name', $self->agentnum);
-  $subject = $options{subject} if exists($options{subject});
-
-  my $notify_template = new Text::Template (TYPE => 'ARRAY',
-                                            SOURCE => [ map "$_\n",
-                                              $conf->config($template)]
-                                           )
-    or die "can't create new Text::Template object: Text::Template::ERROR";
-  $notify_template->compile()
-    or die "can't compile template: Text::Template::ERROR";
+#I<subject> - The subject line of the sent email notification
+#   (default is "Notice from company_name")
 
-  $FS::notify_template::_template::company_name =
-    $conf->config('company_name', $self->agentnum);
-  $FS::notify_template::_template::company_address =
-    join("\n", $conf->config('company_address', $self->agentnum) ). "\n";
+#I<extra_fields> - a hashref of name/value pairs which will be substituted
+#   into the template
 
-  my $paydate = $self->paydate || '2037-12-31';
-  $FS::notify_template::_template::first = $self->first;
-  $FS::notify_template::_template::last = $self->last;
-  $FS::notify_template::_template::company = $self->company;
-  $FS::notify_template::_template::payinfo = $self->mask_payinfo;
-  my $payby = $self->payby;
-  my ($payyear,$paymonth,$payday) = split (/-/,$paydate);
-  my $expire_time = timelocal(0,0,0,$payday,--$paymonth,$payyear);
+#The following variables are vavailable in the template.
 
-  #credit cards expire at the end of the month/year of their exp date
-  if ($payby eq 'CARD' || $payby eq 'DCRD') {
-    $FS::notify_template::_template::payby = 'credit card';
-    ($paymonth < 11) ? $paymonth++ : ($paymonth=0, $payyear++);
-    $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear);
-    $expire_time--;
-  }elsif ($payby eq 'COMP') {
-    $FS::notify_template::_template::payby = 'complimentary account';
-  }else{
-    $FS::notify_template::_template::payby = 'current method';
-  }
-  $FS::notify_template::_template::expdate = $expire_time;
+#I<$first> - the customer first name
+#I<$last> - the customer last name
+#I<$company> - the customer company
+#I<$payby> - a description of the method of payment for the customer
+#            # would be nice to use FS::payby::shortname
+#I<$payinfo> - the account information used to collect for this customer
+#I<$expdate> - the expiration of the customer payment in seconds from epoch
 
-  for (keys %{$options{extra_fields}}){
-    no strict "refs";
-    ${"FS::notify_template::_template::$_"} = $options{extra_fields}->{$_};
-  }
+#=cut
 
-  send_email(from => $from,
-             to => $to,
-             subject => $subject,
-             body => $notify_template->fill_in( PACKAGE =>
-                                                'FS::notify_template::_template'                                              ),
-            );
+#sub notify {
+#  my ($self, $template, %options) = @_;
+
+#  return unless $conf->exists($template);
+
+#  my $from = $conf->invoice_from_full($self->agentnum)
+#    if $conf->exists('invoice_from', $self->agentnum);
+#  $from = $options{from} if exists($options{from});
+
+#  my $to = join(',', $self->invoicing_list_emailonly);
+#  $to = $options{to} if exists($options{to});
+#  
+#  my $subject = "Notice from " . $conf->config('company_name', $self->agentnum)
+#    if $conf->exists('company_name', $self->agentnum);
+#  $subject = $options{subject} if exists($options{subject});
+
+#  my $notify_template = new Text::Template (TYPE => 'ARRAY',
+#                                            SOURCE => [ map "$_\n",
+#                                              $conf->config($template)]
+#                                           )
+#    or die "can't create new Text::Template object: Text::Template::ERROR";
+#  $notify_template->compile()
+#    or die "can't compile template: Text::Template::ERROR";
+
+#  $FS::notify_template::_template::company_name =
+#    $conf->config('company_name', $self->agentnum);
+#  $FS::notify_template::_template::company_address =
+#    join("\n", $conf->config('company_address', $self->agentnum) ). "\n";
+
+#  my $paydate = $self->paydate || '2037-12-31';
+#  $FS::notify_template::_template::first = $self->first;
+#  $FS::notify_template::_template::last = $self->last;
+#  $FS::notify_template::_template::company = $self->company;
+#  $FS::notify_template::_template::payinfo = $self->mask_payinfo;
+#  my $payby = $self->payby;
+#  my ($payyear,$paymonth,$payday) = split (/-/,$paydate);
+#  my $expire_time = timelocal(0,0,0,$payday,--$paymonth,$payyear);
+
+#  #credit cards expire at the end of the month/year of their exp date
+#  if ($payby eq 'CARD' || $payby eq 'DCRD') {
+#    $FS::notify_template::_template::payby = 'credit card';
+#    ($paymonth < 11) ? $paymonth++ : ($paymonth=0, $payyear++);
+#    $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear);
+#    $expire_time--;
+#  }elsif ($payby eq 'COMP') {
+#    $FS::notify_template::_template::payby = 'complimentary account';
+#  }else{
+#    $FS::notify_template::_template::payby = 'current method';
+#  }
+#  $FS::notify_template::_template::expdate = $expire_time;
+
+#  for (keys %{$options{extra_fields}}){
+#    no strict "refs";
+#    ${"FS::notify_template::_template::$_"} = $options{extra_fields}->{$_};
+#  }
+
+#  send_email(from => $from,
+#             to => $to,
+#             subject => $subject,
+#             body => $notify_template->fill_in( PACKAGE =>
+#                                                'FS::notify_template::_template'                                              ),
+#            );
 
-}
+#}
 
 =item generate_letter CUSTOMER_OBJECT TEMPLATE_NAME OPTIONS
 
@@ -4822,7 +4864,9 @@ sub queueable_print {
   my $self = qsearchs('cust_main', { 'custnum' => $opt{custnum} } )
     or die "invalid customer number: " . $opt{custnum};
 
-  my $error = $self->print( { 'template' => $opt{template} } );
+#do not backport this change to 3.x
+#  my $error = $self->print( { 'template' => $opt{template} } );
+  my $error = $self->print( $opt{'template'} );
   die $error if $error;
 }