add northern cyprus, RT#39335
[freeside.git] / FS / FS / cust_main.pm
index f2c2b4a..4fb4f52 100644 (file)
@@ -28,11 +28,10 @@ use Date::Format;
 #use Date::Manip;
 use File::Temp; #qw( tempfile );
 use Business::CreditCard 0.28;
 #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::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 );
+use FS::Misc qw( generate_ps do_print money_pretty );
 use FS::Msgcat qw(gettext);
 use FS::CurrentUser;
 use FS::TicketSystem;
 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 );
 
   );
   $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>.
 
 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<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
 
 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 : {};
 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"
   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;
 
   warn "  setting customer tags\n"
     if $DEBUG > 1;
 
@@ -572,6 +555,25 @@ sub insert {
   warn "  setting contacts\n"
     if $DEBUG > 1;
 
   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 ) {
   if ( my $contact = delete $options{'contact'} ) {
 
     foreach my $c ( @$contact ) {
@@ -599,7 +601,18 @@ sub insert {
   warn "  setting cust_payby\n"
     if $DEBUG > 1;
 
   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',
 
     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.
 
 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:
+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.
 
 
-  $new_cust_main->replace( $old_cust_main, [ $email, 'POST' ] );
-
-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.
 
 
 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 {
 =cut
 
 sub replace {
@@ -1336,6 +1355,54 @@ sub replace {
     $self->set($l.'num', $new_loc->locationnum);
   } #for $l
 
     $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);
 
   # 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
   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 ) {
 
   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 ) {
   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_alphan('po_number')
     || $self->ut_enum('complimentary', [ '', 'Y' ])
     || $self->ut_flag('invoice_ship_address')
+    || $self->ut_flag('invoice_dest')
   ;
 
   foreach (qw(company ship_company)) {
   ;
 
   foreach (qw(company ship_company)) {
@@ -2803,18 +2877,10 @@ sub tax_exemption {
 
 =item cust_main_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
 
 
 =cut
 
@@ -2822,47 +2888,13 @@ sub invoicing_list {
   my( $self, $arrayref ) = @_;
 
   if ( $arrayref ) {
   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
 }
 
 =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.
 =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;
 
 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
 }
 
 =item invoicing_list_emailonly
@@ -2958,7 +2979,16 @@ sub invoicing_list_emailonly {
   my $self = shift;
   warn "$me invoicing_list_emailonly called"
     if $DEBUG;
   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
 }
 
 =item invoicing_list_emailonly_scalar
@@ -3838,26 +3868,14 @@ sub ship_contact_firstlast {
   $contact->get('first') . ' '. $contact->get('last');
 }
 
   $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;
 sub bill_country_full {
   my $self = shift;
-  code2country($self->bill_location->country);
+  $self->bill_location->country_full;
 }
 
 sub ship_country_full {
   my $self = shift;
 }
 
 sub ship_country_full {
   my $self = shift;
-  code2country($self->ship_location->country);
+  $self->ship_location->country_full;
 }
 
 =item county_state_county [ PREFIX ]
 }
 
 =item county_state_county [ PREFIX ]
@@ -4053,6 +4071,30 @@ sub tickets {
   (@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;
 # Return services representing svc_accts in customer support packages
 sub support_services {
   my $self = shift;
@@ -4175,7 +4217,7 @@ sub payment_history {
         'amount'      => sprintf('%.2f', $_->setup + $_->recur ),
         'charged'     => sprintf('%.2f', $_->setup + $_->recur ),
         'date'        => $cust_bill->_date,
         'amount'      => sprintf('%.2f', $_->setup + $_->recur ),
         'charged'     => sprintf('%.2f', $_->setup + $_->recur ),
         'date'        => $cust_bill->_date,
-        'date_pretty' =>  time2str('%m/%d/%Y', $cust_bill->_date ),
+        'date_pretty' => $self->time2str_local('short', $cust_bill->_date ),
       }
         foreach $cust_bill->cust_bill_pkg;
 
       }
         foreach $cust_bill->cust_bill_pkg;
 
@@ -4189,7 +4231,7 @@ sub payment_history {
                      'amount'      => sprintf('%.2f', $_->charged ),
                      'charged'     => sprintf('%.2f', $_->charged ),
                      'date'        => $_->_date,
                      'amount'      => sprintf('%.2f', $_->charged ),
                      'charged'     => sprintf('%.2f', $_->charged ),
                      'date'        => $_->_date,
-                     'date_pretty' =>  time2str('%m/%d/%Y', $_->_date ),
+                     'date_pretty' => $self->time2str_local('short', $_->_date ),
                    }
       foreach $self->cust_bill;
 
                    }
       foreach $self->cust_bill;
 
@@ -4201,7 +4243,7 @@ sub payment_history {
                    'amount'      => sprintf('%.2f', 0 - $_->paid ),
                    'paid'        => sprintf('%.2f', $_->paid ),
                    'date'        => $_->_date,
                    'amount'      => sprintf('%.2f', 0 - $_->paid ),
                    'paid'        => sprintf('%.2f', $_->paid ),
                    'date'        => $_->_date,
-                   'date_pretty' =>  time2str('%m/%d/%Y', $_->_date ),
+                   'date_pretty' => $self->time2str_local('short', $_->_date ),
                  }
     foreach $self->cust_pay;
 
                  }
     foreach $self->cust_pay;
 
@@ -4211,7 +4253,7 @@ sub payment_history {
                    'amount'      => sprintf('%.2f', 0 -$_->amount ),
                    'credit'      => sprintf('%.2f', $_->amount ),
                    'date'        => $_->_date,
                    'amount'      => sprintf('%.2f', 0 -$_->amount ),
                    'credit'      => sprintf('%.2f', $_->amount ),
                    'date'        => $_->_date,
-                   'date_pretty' =>  time2str('%m/%d/%Y', $_->_date ),
+                   'date_pretty' => $self->time2str_local('short', $_->_date ),
                  }
     foreach $self->cust_credit;
 
                  }
     foreach $self->cust_credit;
 
@@ -4221,7 +4263,7 @@ sub payment_history {
                    'amount'      => $_->refund,
                    'refund'      => $_->refund,
                    'date'        => $_->_date,
                    'amount'      => $_->refund,
                    'refund'      => $_->refund,
                    'date'        => $_->_date,
-                   'date_pretty' =>  time2str('%m/%d/%Y', $_->_date ),
+                   'date_pretty' => $self->time2str_local('short', $_->_date ),
                  }
     foreach $self->cust_refund;
 
                  }
     foreach $self->cust_refund;
 
@@ -4241,8 +4283,7 @@ sub payment_history {
     }
     $$item{'balance'} = sprintf("%.2f",$balance);
     foreach my $key ( qw(amount balance) ) {
     }
     $$item{'balance'} = sprintf("%.2f",$balance);
     foreach my $key ( qw(amount balance) ) {
-      $$item{$key.'_pretty'} = $$item{$key};
-      $$item{$key.'_pretty'} =~ s/^(-?)/$1$money_char/;
+      $$item{$key.'_pretty'} = money_pretty($$item{$key});
     }
     push(@out,$item);
   }
     }
     push(@out,$item);
   }
@@ -4254,6 +4295,8 @@ sub payment_history {
       'description' => 'Previous balance',
       'amount'      => sprintf("%.2f",$previous),
       'balance'     => sprintf("%.2f",$previous),
       'description' => 'Previous balance',
       'amount'      => sprintf("%.2f",$previous),
       'balance'     => sprintf("%.2f",$previous),
+      'date'        => $$opt{'start_date'},
+      'date_pretty' => $self->time2str_local('short', $$opt{'start_date'} ),
     };
     #false laziness with above
     foreach my $key ( qw(amount balance) ) {
     };
     #false laziness with above
     foreach my $key ( qw(amount balance) ) {
@@ -4573,102 +4616,102 @@ sub search {
 
 =over 4
 
 
 =over 4
 
-=item notify CUSTOMER_OBJECT TEMPLATE_NAME OPTIONS
-
-Deprecated.  Use event notification and message templates 
-(L<FS::msg_template>) instead.
+#=item notify CUSTOMER_OBJECT TEMPLATE_NAME OPTIONS
 
 
-Sends a templated email notification to the customer (see L<Text::Template>).
-
-OPTIONS is a hash and may include
+#Deprecated.  Use event notification and message templates 
+#(L<FS::msg_template>) instead.
 
 
-I<from> - the email sender (default is invoice_from)
+#Sends a templated email notification to the customer (see L<Text::Template>).
 
 
-I<to> - comma-separated scalar or arrayref of recipients 
-   (default is invoicing_list)
+#OPTIONS is a hash and may include
 
 
-I<subject> - The subject line of the sent email notification
-   (default is "Notice from company_name")
+#I<from> - the email sender (default is invoice_from)
 
 
-I<extra_fields> - a hashref of name/value pairs which will be substituted
-   into the template
+#I<to> - comma-separated scalar or arrayref of recipients 
+#   (default is invoicing_list)
 
 
-The following variables are vavailable in the template.
+#I<subject> - The subject line of the sent email notification
+#   (default is "Notice from company_name")
 
 
-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<extra_fields> - a hashref of name/value pairs which will be substituted
+#   into the template
 
 
-=cut
+#The following variables are vavailable in the template.
 
 
-sub notify {
-  my ($self, $template, %options) = @_;
+#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
 
 
-  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}->{$_};
-  }
+#=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
 
 
 =item generate_letter CUSTOMER_OBJECT TEMPLATE_NAME OPTIONS
 
@@ -4680,6 +4723,8 @@ I<extra_fields> - a hashref of name/value pairs which will be substituted
    into the template.  These values may override values mentioned below
    and those from the customer record.
 
    into the template.  These values may override values mentioned below
    and those from the customer record.
 
+I<template_text> - if present, ignores TEMPLATE_NAME and uses the provided text
+
 The following variables are available in the template instead of or in addition
 to the fields of the customer record.
 
 The following variables are available in the template instead of or in addition
 to the fields of the customer record.
 
@@ -4695,11 +4740,16 @@ I<$returnaddress> - the return address defaults to invoice_latexreturnaddress or
 sub generate_letter {
   my ($self, $template, %options) = @_;
 
 sub generate_letter {
   my ($self, $template, %options) = @_;
 
-  return unless $conf->exists($template);
+  warn "Template $template does not exist" && return
+    unless $conf->exists($template) || $options{'template_text'};
+
+  my $template_source = $options{'template_text'} 
+                        ? [ $options{'template_text'} ] 
+                        : [ map "$_\n", $conf->config($template) ];
 
   my $letter_template = new Text::Template
                         ( TYPE       => 'ARRAY',
 
   my $letter_template = new Text::Template
                         ( TYPE       => 'ARRAY',
-                          SOURCE     => [ map "$_\n", $conf->config($template)],
+                          SOURCE     => $template_source,
                           DELIMITERS => [ '[@--', '--@]' ],
                         )
     or die "can't create new Text::Template object: Text::Template::ERROR";
                           DELIMITERS => [ '[@--', '--@]' ],
                         )
     or die "can't create new Text::Template object: Text::Template::ERROR";
@@ -4814,7 +4864,9 @@ sub queueable_print {
   my $self = qsearchs('cust_main', { 'custnum' => $opt{custnum} } )
     or die "invalid customer number: " . $opt{custnum};
 
   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;
 }
 
   die $error if $error;
 }