suretax-hostname option, and minor UI fix, #33015
[freeside.git] / FS / FS / Conf.pm
index ca6588e..8cd8e27 100644 (file)
@@ -1,6 +1,9 @@
 package FS::Conf;
 
-use vars qw($base_dir @config_items @base_items @card_types $DEBUG);
+use strict;
+use vars qw( $base_dir @config_items @base_items @card_types $DEBUG
+             $conf_cache $conf_cache_enabled
+           );
 use Carp;
 use IO::File;
 use File::Basename;
@@ -8,18 +11,19 @@ use MIME::Base64;
 use Locale::Currency;
 use FS::ConfItem;
 use FS::ConfDefaults;
-use FS::Conf_compat17;
 use FS::Locales;
 use FS::payby;
 use FS::conf;
 use FS::Record qw(qsearch qsearchs);
-use FS::UID qw(dbh datasrc use_confcompat);
+use FS::UID qw(dbh datasrc);
 use FS::Misc::Invoicing qw( spool_formats );
 
 $base_dir = '%%%FREESIDE_CONF%%%';
 
 $DEBUG = 0;
 
+$conf_cache_enabled = 0;
+
 =head1 NAME
 
 FS::Conf - Freeside configuration values
@@ -108,18 +112,12 @@ specific value(s) is returned.
 
 =cut
 
-sub _usecompat {
-  my ($self, $method) = (shift, shift);
-  carp "NO CONFIGURATION RECORDS FOUND -- USING COMPATIBILITY MODE"
-    if use_confcompat;
-  my $compat = new FS::Conf_compat17 ("$base_dir/conf." . datasrc);
-  $compat->$method(@_);
-}
-
 sub _config {
   my($self,$name,$agentnum,$agentonly)=@_;
   my $hashref = { 'name' => $name };
   local $FS::Record::conf = undef;  # XXX evil hack prevents recursion
+  $conf_cache = undef unless $conf_cache_enabled; # use cache only when it is
+                                                  # safe to do so
   my $cv;
   my @a = (
     ($agentnum || ()),
@@ -133,9 +131,14 @@ sub _config {
   foreach my $a (@a) {
     $hashref->{agentnum} = $a;
     foreach my $l (@l) {
-      $hashref->{locale} = $l;
-      $cv = FS::Record::qsearchs('conf', $hashref);
-      return $cv if $cv;
+      my $key = join(':',$name, $a, $l);
+      if (! exists $conf_cache->{$key}){
+        $hashref->{locale} = $l;
+        # $conf_cache is reset in FS::UID during myconnect, so the cache is
+        # reset per connection
+        $conf_cache->{$key} = FS::Record::qsearchs('conf', $hashref);
+      }
+      return $conf_cache->{$key} if $conf_cache->{$key};
     }
   }
   return undef;
@@ -143,7 +146,6 @@ sub _config {
 
 sub config {
   my $self = shift;
-  return $self->_usecompat('config', @_) if use_confcompat;
 
   carp "FS::Conf->config(". join(', ', @_). ") called"
     if $DEBUG > 1;
@@ -167,7 +169,6 @@ Returns the exact scalar value for key.
 
 sub config_binary {
   my $self = shift;
-  return $self->_usecompat('config_binary', @_) if use_confcompat;
 
   my $cv = $self->_config(@_) or return;
   length($cv->value) ? decode_base64($cv->value) : '';
@@ -182,7 +183,6 @@ is undefined.
 
 sub exists {
   my $self = shift;
-  return $self->_usecompat('exists', @_) if use_confcompat;
 
   #my($name, $agentnum)=@_;
 
@@ -197,7 +197,6 @@ sub exists {
 
 sub config_bool {
   my $self = shift;
-  return $self->_usecompat('exists', @_) if use_confcompat;
 
   my($name,$agentnum,$agentonly) = @_;
 
@@ -252,7 +251,6 @@ KEY_SUFFIX, if it exists, otherwise for KEY
 # these to fall back to standard values
 sub config_orbase {
   my $self = shift;
-  return $self->_usecompat('config_orbase', @_) if use_confcompat;
 
   my( $name, $suffix ) = @_;
   if ( $self->exists("${name}_$suffix") ) {
@@ -272,7 +270,6 @@ config_orbase.
 
 sub key_orbase {
   my $self = shift;
-  #no compat for this...return $self->_usecompat('config_orbase', @_) if use_confcompat;
 
   my( $name, $suffix ) = @_;
   if ( $self->exists("${name}_$suffix") ) {
@@ -315,7 +312,6 @@ Creates the specified configuration key if it does not exist.
 
 sub touch {
   my $self = shift;
-  return $self->_usecompat('touch', @_) if use_confcompat;
 
   my($name, $agentnum) = @_;
   #unless ( $self->exists($name, $agentnum) ) {
@@ -336,7 +332,6 @@ Sets the specified configuration key to the given value.
 
 sub set {
   my $self = shift;
-  return $self->_usecompat('set', @_) if use_confcompat;
 
   my($name, $value, $agentnum) = @_;
   $value =~ /^(.*)$/s;
@@ -361,6 +356,12 @@ sub set {
     $error = $new->insert;
   }
 
+  if (! $error) {
+    # clean the object cache
+    my $key = join(':',$name, $agentnum, $self->{locale});
+    $conf_cache->{ $key } = $new;
+  }
+
   die "error setting configuration value: $error \n"
     if $error;
 
@@ -375,7 +376,6 @@ can be retrieved with config_binary.
 
 sub set_binary {
   my $self  = shift;
-  return if use_confcompat;
 
   my($name, $value, $agentnum)=@_;
   $self->set($name, encode_base64($value), $agentnum);
@@ -389,7 +389,6 @@ Deletes the specified configuration key.
 
 sub delete {
   my $self = shift;
-  return $self->_usecompat('delete', @_) if use_confcompat;
 
   my($name, $agentnum) = @_;
   if ( my $cv = FS::Record::qsearchs('conf', {name => $name, agentnum => $agentnum, locale => $self->{locale}}) ) {
@@ -416,7 +415,6 @@ sub delete {
 
 sub delete_bool {
   my $self = shift;
-  return $self->_usecompat('delete', @_) if use_confcompat;
 
   my($name, $agentnum) = @_;
 
@@ -447,7 +445,7 @@ in the directory DIR.
 sub import_config_item { 
   my ($self,$item,$dir) = @_;
   my $key = $item->key;
-  if ( -e "$dir/$key" && ! use_confcompat ) {
+  if ( -e "$dir/$key" ) {
     warn "Inserting $key\n" if $DEBUG;
     local $/;
     my $value = readline(new IO::File "$dir/$key");
@@ -456,68 +454,9 @@ sub import_config_item {
     }else{
       $self->set($key, $value);
     }
-  }else {
-    warn "Not inserting $key\n" if $DEBUG;
-  }
-}
-
-=item verify_config_item CONFITEM DIR 
-
-  Compares the item specified by the CONFITEM (see L<FS::ConfItem>) in
-the database to the legacy file value in DIR.
-
-=cut
-
-sub verify_config_item { 
-  return '' if use_confcompat;
-  my ($self,$item,$dir) = @_;
-  my $key = $item->key;
-  my $type = $item->type;
-
-  my $compat = new FS::Conf_compat17 $dir;
-  my $error = '';
-  
-  $error .= "$key fails existential comparison; "
-    if $self->exists($key) xor $compat->exists($key);
-
-  if ( $type !~ /^(binary|image)$/ ) {
-
-    {
-      no warnings;
-      $error .= "$key fails scalar comparison; "
-        unless scalar($self->config($key)) eq scalar($compat->config($key));
-    }
-
-    my (@new) = $self->config($key);
-    my (@old) = $compat->config($key);
-    unless ( scalar(@new) == scalar(@old)) { 
-      $error .= "$key fails list comparison; ";
-    }else{
-      my $r=1;
-      foreach (@old) { $r=0 if ($_ cmp shift(@new)); }
-      $error .= "$key fails list comparison; "
-        unless $r;
-    }
-
   } else {
-
-    no warnings 'uninitialized';
-    $error .= "$key fails binary comparison; "
-      unless scalar($self->config_binary($key)) eq scalar($compat->config_binary($key));
-
+    warn "Not inserting $key\n" if $DEBUG;
   }
-
-#remove deprecated config on our own terms, not freeside-upgrade's
-#  if ($error =~ /existential comparison/ && $item->section eq 'deprecated') {
-#    my $proto;
-#    for ( @config_items ) { $proto = $_; last if $proto->key eq $key;  }
-#    unless ($proto->key eq $key) { 
-#      warn "removed config item $error\n" if $DEBUG;
-#      $error = '';
-#    }
-#  }
-
-  $error;
 }
 
 #item _orbase_items OPTIONS
@@ -588,11 +527,25 @@ FS::ConfItem objects.  See L<FS::ConfItem>.
 
 sub config_items {
   my $self = shift; 
-  return $self->_usecompat('config_items', @_) if use_confcompat;
 
   ( @config_items, $self->_orbase_items(@_) );
 }
 
+=item invoice_from_full [ AGENTNUM ]
+
+Returns values of invoice_from and invoice_from_name, appropriately combined
+based on their current values.
+
+=cut
+
+sub invoice_from_full {
+  my ($self, $agentnum) = @_;
+  return $self->config('invoice_from_name', $agentnum ) ?
+         $self->config('invoice_from_name', $agentnum ) . ' <' .
+         $self->config('invoice_from', $agentnum ) . '>' :
+         $self->config('invoice_from', $agentnum );
+}
+
 =back
 
 =head1 SUBROUTINES
@@ -609,23 +562,11 @@ to conf records in the database.
 sub init_config {
   my $dir = shift;
 
-  {
-    local $FS::UID::use_confcompat = 0;
-    my $conf = new FS::Conf;
-    foreach my $item ( $conf->config_items(dir => $dir) ) {
-      $conf->import_config_item($item, $dir);
-      my $error = $conf->verify_config_item($item, $dir);
-      return $error if $error;
-    }
-  
-    my $compat = new FS::Conf_compat17 $dir;
-    foreach my $item ( $compat->config_items ) {
-      my $error = $conf->verify_config_item($item, $dir);
-      return $error if $error;
-    }
+  my $conf = new FS::Conf;
+  foreach my $item ( $conf->config_items(dir => $dir) ) {
+    $conf->import_config_item($item, $dir);
   }
 
-  $FS::UID::use_confcompat = 0;
   '';  #success
 }
 
@@ -718,6 +659,23 @@ my %batch_gateway_options = (
   },
 );
 
+my %invoice_mode_options = (
+  'type'        => 'select-sub',
+  'options_sub' => sub { 
+    my @modes = qsearch({
+        'table' => 'invoice_mode', 
+        'extra_sql' => ' WHERE '.
+          $FS::CurrentUser::CurrentUser->agentnums_sql(null => 1),
+        });
+    map { $_->modenum, $_->modename } @modes;
+  },
+  'option_sub'  => sub { 
+                         my $mode = FS::invoice_mode->by_key(shift);
+                         $mode ? $mode->modename : '',
+                       },
+  'per_agent' => 1,
+);
+
 my @cdr_formats = (
   '' => '',
   'default' => 'Default',
@@ -773,7 +731,7 @@ sub reason_type_options {
   {
     'key'         => 'log_sent_mail',
     'section'     => 'notification',
-    'description' => 'Enable logging of template-generated email.',
+    'description' => 'Enable logging of all sent email.',
     'type'        => 'checkbox',
   },
 
@@ -958,7 +916,7 @@ sub reason_type_options {
   {
     'key'         => 'business-onlinepayment',
     'section'     => 'billing',
-    'description' => '<a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> support, at least three lines: processor, login, and password.  An optional fourth line specifies the action or actions (multiple actions are separated with `,\': for example: `Authorization Only, Post Authorization\').    Optional additional lines are passed to Business::OnlinePayment as %processor_options.',
+    'description' => '<a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> support, at least three lines: processor, login, and password.  An optional fourth line specifies the action or actions (multiple actions are separated with `,\': for example: `Authorization Only, Post Authorization\').    Optional additional lines are passed to Business::OnlinePayment as %processor_options.  For more detailed information and examples see the <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:3:Documentation:Administration:Real-time_Processing">real-time credit card processing documentation</a>.',
     'type'        => 'textarea',
   },
 
@@ -1233,12 +1191,36 @@ sub reason_type_options {
   {
     'key'         => 'invoice_from',
     'section'     => 'required',
-    'description' => 'Return address on email invoices',
+    'description' => 'Return address on email invoices (address only, see invoice_from_name)',
+    'type'        => 'text',
+    'per_agent'   => 1,
+    'validate'    => sub { $_[0] =~
+                             /^[^@]+\@[[:alnum:]-]+(\.[[:alnum:]-]+)+$/
+                             ? '' : 'Invalid email address';
+                         }
+  },
+
+  {
+    'key'         => 'invoice_from_name',
+    'section'     => 'invoicing',
+    'description' => 'Return name on email invoices (set address in invoice_from)',
     'type'        => 'text',
     'per_agent'   => 1,
+    'validate'    => sub { (($_[0] =~ /[^[:alnum:][:space:]]/) && ($_[0] !~ /^\".*\"$/))
+                           ? 'Invalid name.  Use quotation marks around names that contain punctuation.'
+                           : '' }
   },
 
   {
+    'key'         => 'quotation_from',
+    'section'     => '',
+    'description' => 'Return address on email quotations',
+    'type'        => 'text',
+    'per_agent'   => 1,
+  },
+
+
+  {
     'key'         => 'invoice_subject',
     'section'     => 'invoicing',
     'description' => 'Subject: header on email invoices.  Defaults to "Invoice".  The following substitutions are available: $name, $name_short, $invoice_number, and $invoice_date.',
@@ -1248,6 +1230,15 @@ sub reason_type_options {
   },
 
   {
+    'key'         => 'quotation_subject',
+    'section'     => '',
+    'description' => 'Subject: header on email quotations.  Defaults to "Quotation".', #  The following substitutions are available: $name, $name_short, $invoice_number, and $invoice_date.',
+    'type'        => 'text',
+    #'per_agent'   => 1,
+    'per_locale'  => 1,
+  },
+
+  {
     'key'         => 'invoice_usesummary',
     'section'     => 'invoicing',
     'description' => 'Indicates that html and latex invoices should be in summary style and make use of invoice_latexsummary.',
@@ -1502,14 +1493,35 @@ and customer address. Include units.',
   {
     'key'         => 'invoice_email_pdf',
     'section'     => 'invoicing',
-    'description' => 'Send PDF invoice as an attachment to emailed invoices.  By default, includes the plain text invoice as the email body, unless invoice_email_pdf_note is set.',
+    'description' => 'Send PDF invoice as an attachment to emailed invoices.  By default, includes the HTML invoice as the email body, unless invoice_email_pdf_note is set.',
     'type'        => 'checkbox'
   },
 
   {
+    'key'         => 'quotation_email_pdf',
+    'section'     => '',
+    'description' => 'Send PDF quotations as an attachment to emailed quotations.  By default, includes the HTML quotation as the email body, unless quotation_email_pdf_note is set.',
+    'type'        => 'checkbox'
+  },
+
+  {
+    'key'         => 'invoice_email_pdf_msgnum',
+    'section'     => 'invoicing',
+    'description' => 'Message template to send as the text and HTML part of PDF invoices. If not selected, a text and HTML version of the invoice will be sent.',
+    %msg_template_options,
+  },
+
+  {
     'key'         => 'invoice_email_pdf_note',
     'section'     => 'invoicing',
-    'description' => 'If defined, this text will replace the default plain text invoice as the body of emailed PDF invoices.',
+    'description' => 'If defined, this text will replace the default HTML invoice as the body of emailed PDF invoices.',
+    'type'        => 'textarea'
+  },
+
+  {
+    'key'         => 'quotation_email_pdf_note',
+    'section'     => '',
+    'description' => 'If defined, this text will replace the default HTML quotation as the body of emailed PDF quotations.',
     'type'        => 'textarea'
   },
 
@@ -1539,6 +1551,7 @@ and customer address. Include units.',
     'section'     => 'invoicing',
     'description' => 'Optional default invoice term, used to calculate a due date printed on invoices.',
     'type'        => 'select',
+    'per_agent'   => 1,
     'select_enum' => [ 
       '', 'Payable upon receipt', 'Net 0', 'Net 3', 'Net 5', 'Net 9', 'Net 10', 'Net 14', 
       'Net 15', 'Net 18', 'Net 20', 'Net 21', 'Net 25', 'Net 30', 'Net 45', 
@@ -1651,9 +1664,16 @@ and customer address. Include units.',
   },
 
   {
+    'key'         => 'payment_receipt_statement_mode',
+    'section'     => 'notification',
+    'description' => 'Automatic payments will cause a post-payment statement to be sent to the customer. Select the invoice mode to use for this statement. If unspecified, it will use the "_statement" versions of invoice configuration settings, and have the notice name "Statement".',
+    %invoice_mode_options,
+  },
+
+  {
     'key'         => 'payment_receipt_msgnum',
     'section'     => 'notification',
-    'description' => 'Template to use for payment receipts.',
+    'description' => 'Template to use for manual payment receipts.',
     %msg_template_options,
   },
   
@@ -2084,13 +2104,6 @@ and customer address. Include units.',
   },
 
   {
-    'key'         => 'safe-part_bill_event',
-    'section'     => 'UI',
-    'description' => 'Validates invoice event expressions against a preset list.  Useful for webdemos, annoying to powerusers.',
-    'type'        => 'checkbox',
-  },
-
-  {
     'key'         => 'show_ship_company',
     'section'     => 'UI',
     'description' => 'Turns on display/collection of a "service company name" field for customers.',
@@ -2186,7 +2199,7 @@ and customer address. Include units.',
     'section'     => 'self-service',
     'description' => 'Acceptable payment types for the signup server',
     'type'        => 'selectmultiple',
-    'select_enum' => [ qw(CARD DCRD CHEK DCHK PREPAY PPAL BILL COMP) ],
+    'select_enum' => [ qw(CARD DCRD CHEK DCHK PREPAY PPAL ) ], # BILL COMP) ],
   },
 
   {
@@ -2438,55 +2451,91 @@ and customer address. Include units.',
 
   {
     'key'         => 'enable_taxclasses',
-    'section'     => 'billing',
+    'section'     => 'taxation',
     'description' => 'Enable per-package tax classes',
     'type'        => 'checkbox',
   },
 
   {
     'key'         => 'require_taxclasses',
-    'section'     => 'billing',
+    'section'     => 'taxation',
     'description' => 'Require a taxclass to be entered for every package',
     'type'        => 'checkbox',
   },
 
   {
-    'key'         => 'enable_taxproducts',
-    'section'     => 'billing',
+    'key'         => 'tax_data_vendor',
+    'section'     => 'taxation',
     'description' => 'Tax data vendor you are using.',
     'type'        => 'select',
-    'select_enum' => [ 'cch', 'billsoft', 'avalara' ],
+    'select_enum' => [ '', 'cch', 'billsoft', 'avalara', 'suretax' ],
   },
 
   {
     'key'         => 'taxdatadirectdownload',
-    'section'     => 'billing',  #well
-    'description' => 'Enable downloading tax data directly from the vendor site. at least three lines: URL, username, and password.j',
+    'section'     => 'taxation',
+    'description' => 'Enable downloading tax data directly from CCH. at least three lines: URL, username, and password.j',
     'type'        => 'textarea',
   },
 
   {
     'key'         => 'ignore_incalculable_taxes',
-    'section'     => 'billing',
+    'section'     => 'taxation',
     'description' => 'Prefer to invoice without tax over not billing at all',
     'type'        => 'checkbox',
   },
 
   {
     'key'         => 'billsoft-company_code',
-    'section'     => 'billing',
+    'section'     => 'taxation',
     'description' => 'Billsoft tax service company code (3 letters)',
     'type'        => 'text',
   },
 
   {
     'key'         => 'avalara-taxconfig',
-    'section'     => 'billing',
+    'section'     => 'taxation',
     'description' => 'Avalara tax service configuration. Four lines: company code, account number, license key, test mode (1 to enable).',
     'type'        => 'textarea',
   },
 
   {
+    'key'         => 'suretax-hostname',
+    'section'     => 'taxation',
+    'description' => 'SureTax server name; defaults to the test server.',
+    'type'        => 'text',
+  },
+
+  {
+    'key'         => 'suretax-client_number',
+    'section'     => 'taxation',
+    'description' => 'SureTax tax service client ID.',
+    'type'        => 'text',
+  },
+  {
+    'key'         => 'suretax-validation_key',
+    'section'     => 'taxation',
+    'description' => 'SureTax validation key (UUID).',
+    'type'        => 'text',
+  },
+  {
+    'key'         => 'suretax-business_unit',
+    'section'     => 'taxation',
+    'description' => 'SureTax client business unit name; optional.',
+    'type'        => 'text',
+    'per_agent'   => 1,
+  },
+  {
+    'key'         => 'suretax-regulatory_code',
+    'section'     => 'taxation',
+    'description' => 'SureTax client regulatory status.',
+    'type'        => 'select',
+    'select_enum' => [ '', 'ILEC', 'IXC', 'CLEC', 'VOIP', 'ISP', 'Wireless' ],
+    'per_agent'   => 1,
+  },
+
+
+  {
     'key'         => 'welcome_msgnum',
     'section'     => 'notification',
     'description' => 'Template to use for welcome messages when a svc_acct record is created.',
@@ -2589,13 +2638,20 @@ and customer address. Include units.',
     'section'     => 'billing',
     'description' => 'Available payment types.',
     'type'        => 'selectmultiple',
-    'select_enum' => [ qw(CARD DCRD CHEK DCHK BILL CASH WEST MCRD PPAL COMP) ],
+    'select_enum' => [ qw(CARD DCRD CHEK DCHK CASH WEST MCRD MCHK PPAL) ],
+  },
+
+  {
+    'key'         => 'banned_pay-pad',
+    'section'     => 'billing',
+    'description' => 'Padding for encrypted storage of banned credit card hashes.  If you already have new-style SHA512 entries in the banned_pay table, do not change as this will invalidate the old entries.',
+    'type'        => 'text',
   },
 
   {
     'key'         => 'payby-default',
-    'section'     => 'UI',
-    'description' => 'Default payment type.  HIDE disables display of billing information and sets customers to BILL.',
+    'section'     => 'deprecated',
+    'description' => 'Deprecated; in 4.x there is no longer the concept of a single "payment type".  Used to indicate the default payment type.  HIDE disables display of billing information and sets customers to BILL.',
     'type'        => 'select',
     'select_enum' => [ '', qw(CARD DCRD CHEK DCHK BILL CASH WEST MCRD PPAL COMP HIDE) ],
   },
@@ -2742,6 +2798,13 @@ and customer address. Include units.',
   },
 
   {
+    'key'         => 'manual_process-single_invoice_amount',
+    'section'     => 'billing',
+    'description' => 'When entering manual credit card and ACH payments, amount will not autofill if the customer has more than one open invoice',
+    'type'        => 'checkbox',
+  },
+
+  {
     'key'         => 'manual_process-pkgpart',
     'section'     => 'billing',
     'description' => 'Package to add to each manual credit card and ACH payment entered by employees from the backend.  Enabling this option may be in violation of your merchant agreement(s), so please check it(/them) carefully before enabling this option.',
@@ -2933,7 +2996,7 @@ and customer address. Include units.',
     'type'        => 'select',
     'select_hash' => [ '' => 'Password reset disabled',
                        'email' => 'Click on a link in email',
-                       'paymask,amount,zip' => 'Click on a link in email, and also verify with credit card (or bank account) last 4 digits, payment amount and zip code',
+                       'paymask,amount,zip' => 'Click on a link in email, and also verify with credit card (or bank account) last 4 digits, payment amount and zip code.  Note: Do not use if you have multi-customer contacts, as they will be unable to reset their passwords.',
                      ],
   },
 
@@ -2991,7 +3054,7 @@ and customer address. Include units.',
                         },
     'option_sub'  => sub { require FS::Record;
                            require FS::agent_type;
-                          my $agent = FS::Record::qsearchs(
+                          my $agent_type = FS::Record::qsearchs(
                             'agent_type', { 'typenum'=>shift }
                           );
                            $agent_type ? $agent_type->atype : '';
@@ -3289,6 +3352,14 @@ and customer address. Include units.',
   },
 
   {
+    'key'         => 'city_not_required',
+    'section'     => 'required',
+    'description' => 'Turn off requirement for a City to be entered for billing & shipping addresses',
+    'type'        => 'checkbox',
+    'per_agent'   => 1,
+  },
+
+  {
     'key'         => 'echeck-void',
     'section'     => 'deprecated',
     'description' => '<B>DEPRECATED</B>, now controlled by ACLs.  Used to enable local-only voiding of echeck payments in addition to refunds against the payment gateway',
@@ -3643,14 +3714,14 @@ and customer address. Include units.',
 
   {
     'key'         => 'tax-ship_address',
-    'section'     => 'billing',
+    'section'     => 'taxation',
     'description' => 'By default, tax calculations are done based on the billing address.  Enable this switch to calculate tax based on the shipping address instead.',
     'type'        => 'checkbox',
   }
 ,
   {
     'key'         => 'tax-pkg_address',
-    'section'     => 'billing',
+    'section'     => 'taxation',
     'description' => 'By default, tax calculations are done based on the billing address.  Enable this switch to calculate tax based on the package address instead (when present).',
     'type'        => 'checkbox',
   },
@@ -3729,7 +3800,7 @@ and customer address. Include units.',
     'type'        => 'select',
     'select_enum' => [ 'NACHA', 'csv-td_canada_trust-merchant_pc_batch',
                        'csv-chase_canada-E-xactBatch', 'BoM', 'PAP',
-                       'paymentech', 'ach-spiritone', 'RBC'
+                       'paymentech', 'ach-spiritone', 'RBC', 'CIBC',
                     ]
   },
 
@@ -3791,7 +3862,7 @@ and customer address. Include units.',
     'type'        => 'select',
     'select_enum' => [ 'NACHA', 'csv-td_canada_trust-merchant_pc_batch', 'BoM',
                        'PAP', 'paymentech', 'ach-spiritone', 'RBC',
-                       'td_eft1464', 'eft_canada'
+                       'td_eft1464', 'eft_canada', 'CIBC'
                      ]
   },
 
@@ -3809,6 +3880,13 @@ and customer address. Include units.',
     'type'        => 'textarea',
   },
 
+{
+    'key'         => 'batchconfig-CIBC',
+    'section'     => 'billing',
+    'description' => 'Configuration for Canadian Imperial Bank of Commerce, six lines: 1. Origin ID, 2. Datacenter, 3. Typecode, 4. Short name, 5. Bank, 6. Bank account',
+    'type'        => 'textarea',
+  },
+
   {
     'key'         => 'batchconfig-PAP',
     'section'     => 'billing',
@@ -3833,7 +3911,7 @@ and customer address. Include units.',
   {
     'key'         => 'batchconfig-RBC',
     'section'     => 'billing',
-    'description' => 'Configuration for Royal Bank of Canada PDS batching, four lines: 1. Client number, 2. Short name, 3. Long name, 4. Transaction code.',
+    'description' => 'Configuration for Royal Bank of Canada PDS batching, five lines: 1. Client number, 2. Short name, 3. Long name, 4. Transaction code 5. (optional) set to TEST to turn on test mode.',
     'type'        => 'textarea',
   },
 
@@ -3847,7 +3925,7 @@ and customer address. Include units.',
   {
     'key'         => 'batchconfig-eft_canada',
     'section'     => 'billing',
-    'description' => 'Configuration for EFT Canada batching, four lines: 1. SFTP username, 2. SFTP password, 3. Transaction code, 4. Number of days to delay process date.  If you are using separate per-agent batches (batch-spoolagent), you must set this option separately for each agent, as the global setting will be ignored.',
+    'description' => 'Configuration for EFT Canada batching, five lines: 1. SFTP username, 2. SFTP password, 3. Business transaction code, 4. Personal transaction code, 5. Number of days to delay process date.  If you are using separate per-agent batches (batch-spoolagent), you must set this option separately for each agent, as the global setting will be ignored.',
     'type'        => 'textarea',
     'per_agent'   => 1,
   },
@@ -4276,8 +4354,22 @@ and customer address. Include units.',
   {
     'key'         => 'previous_balance-exclude_from_total',
     'section'     => 'invoicing',
-    'description' => 'Do not include previous balance in the \'Total\' line.  Only meaningful when invoice_sections is false.  Optionally provide text to override the Total New Charges description',
-    'type'        => [ qw(checkbox text) ],
+    'description' => 'Show separate totals for previous invoice balance and new charges. Only meaningful when invoice_sections is false.',
+    'type'        => 'checkbox',
+  },
+
+  {
+    'key'         => 'previous_balance-text',
+    'section'     => 'invoicing',
+    'description' => 'Text for the label of the total previous balance, when it is shown separately. Defaults to "Previous Balance".',
+    'type'        => 'text',
+  },
+
+  {
+    'key'         => 'previous_balance-text-total_new_charges',
+    'section'     => 'invoicing',
+    'description' => 'Text for the label of the total of new charges, when it is shown separately. If invoice_show_prior_due_date is enabled, the due date of current charges will be appended. Defaults to "Total New Charges".',
+    'type'        => 'text',
   },
 
   {
@@ -4313,6 +4405,7 @@ and customer address. Include units.',
     'section'     => 'invoicing',
     'description' => 'Instead of showing payments (and credits) applied to the invoice, show those received since the previous invoice date.',
     'type'        => 'checkbox',
+                       'uscensus' => 'U.S. Census Bureau',
   },
 
   {
@@ -4410,7 +4503,7 @@ and customer address. Include units.',
 
   {
     'key'         => 'tax_district_method',
-    'section'     => 'UI',
+    'section'     => 'taxation',
     'description' => 'The method to use to look up tax district codes.',
     'type'        => 'select',
     #'select_hash' => [ FS::Misc::Geo::get_district_methods() ],
@@ -4549,6 +4642,16 @@ and customer address. Include units.',
   },
 
   {
+    'key'         => 'part_pkg-delay_cancel-days',
+    'section'     => '',
+    'description' => 'Expire packages in this many days when using delay_cancel (default is 1)',
+    'type'        => 'text',
+    'validate'    => sub { (($_[0] =~ /^\d*$/) && (($_[0] eq '') || $_[0]))
+                           ? 'Must specify an integer number of days'
+                           : '' }
+  },
+
+  {
     'key'         => 'mcp_svcpart',
     'section'     => '',
     'description' => 'Master Control Program svcpart.  Leave this blank.',
@@ -4942,8 +5045,15 @@ and customer address. Include units.',
   },
 
   {
+    'key'         => 'svc_phone-bulk_provision_simple',
+    'section'     => 'telephony',
+    'description' => 'Bulk provision phone numbers with a simple number range instead of from DID vendor orders',
+    'type'        => 'checkbox',
+  },
+
+  {
     'key'         => 'default_phone_countrycode',
-    'section'     => '',
+    'section'     => 'telephony',
     'description' => 'Default countrycode',
     'type'        => 'text',
   },
@@ -5154,19 +5264,30 @@ and customer address. Include units.',
 
   {
     'key'         => 'tax-cust_exempt-groups',
-    'section'     => '',
+    'section'     => 'taxation',
     'description' => 'List of grouping possibilities for tax names, for per-customer exemption purposes, one tax name per line.  For example, "GST" would indicate the ability to exempt customers individually from taxes named "GST" (but not other taxes).',
     'type'        => 'textarea',
   },
 
   {
     'key'         => 'tax-cust_exempt-groups-require_individual_nums',
-    'section'     => '',
-    'description' => 'When using tax-cust_exempt-groups, require an individual tax exemption number for each exemption from different taxes.',
+    'section'     => 'deprecated',
+    'description' => 'Deprecated: see tax-cust_exempt-groups-number_requirement',
     'type'        => 'checkbox',
   },
 
   {
+    'key'         => 'tax-cust_exempt-groups-num_req',
+    'section'     => 'taxation',
+    'description' => 'When using tax-cust_exempt-groups, control whether individual tax exemption numbers are required for exemption from different taxes.',
+    'type'        => 'select',
+    'select_hash' => [ ''            => 'Not required',
+                       'residential' => 'Required for residential customers only',
+                       'all'         => 'Required for all customers',
+                     ],
+  },
+
+  {
     'key'         => 'cust_main-default_view',
     'section'     => 'UI',
     'description' => 'Default customer view, for users who have not selected a default view in their preferences.',
@@ -5185,7 +5306,7 @@ and customer address. Include units.',
 
   {
     'key'         => 'enable_tax_adjustments',
-    'section'     => 'billing',
+    'section'     => 'taxation',
     'description' => 'Enable the ability to add manual tax adjustments.',
     'type'        => 'checkbox',
   },
@@ -5638,7 +5759,7 @@ and customer address. Include units.',
 
   {
     'key'         => 'cust_class-tax_exempt',
-    'section'     => 'billing',
+    'section'     => 'taxation',
     'description' => 'Control the tax exemption flag per customer class rather than per indivual customer.',
     'type'        => 'checkbox',
   },
@@ -5819,6 +5940,13 @@ and customer address. Include units.',
     'type'        => 'checkbox',
   },
 
+  {
+    'key'         => 'cust_main-default_commercial',
+    'section'     => 'UI',
+    'description' => 'Default for new customers is commercial rather than residential.',
+    'type'        => 'checkbox',
+  },
+
   { key => "apacheroot", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
   { key => "apachemachine", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
   { key => "apachemachines", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },