Merge branch 'master' of https://github.com/rvandam/Freeside
authorIvan Kohler <ivan@freeside.biz>
Mon, 18 Nov 2013 08:20:37 +0000 (00:20 -0800)
committerIvan Kohler <ivan@freeside.biz>
Mon, 18 Nov 2013 08:20:37 +0000 (00:20 -0800)
59 files changed:
FS/FS/AccessRight.pm
FS/FS/ClientAPI/MyAccount.pm
FS/FS/Conf.pm
FS/FS/Record.pm
FS/FS/Report/Table.pm
FS/FS/Schema.pm
FS/FS/Template_Mixin.pm
FS/FS/UI/Web.pm
FS/FS/cdr.pm
FS/FS/contact_phone.pm
FS/FS/cust_bill.pm
FS/FS/cust_main.pm
FS/FS/cust_main/Search.pm
FS/FS/cust_pay.pm
FS/FS/cust_pay_batch.pm
FS/FS/cust_pay_pending.pm
FS/FS/cust_pay_void.pm
FS/FS/cust_pkg.pm
FS/FS/cust_refund.pm
FS/FS/part_event/Action/letter.pm
FS/FS/part_event/Action/notice.pm
FS/FS/part_event/Action/notice_to.pm
FS/FS/part_event/Action/svc_acct_notice.pm
FS/FS/part_export/rt_ticket.pm
FS/FS/part_export/send_email.pm
FS/FS/part_export/shellcommands.pm
FS/FS/part_pkg/bulk_Common.pm
FS/FS/queue.pm
FS/FS/svc_MAC_Mixin.pm [new file with mode: 0644]
FS/FS/svc_broadband.pm
FS/FS/svc_cable.pm
FS/FS/svc_phone.pm
FS/bin/freeside-queued
FS/bin/freeside-upgrade
bin/restore-ship_company [new file with mode: 0644]
bin/test_scrub
fs_selfservice/FS-SelfService/SelfService/FreeRadiusVoip.pm
httemplate/browse/msg_template.html
httemplate/edit/cust_main.cgi
httemplate/edit/cust_main/before_ship_location.html [new file with mode: 0644]
httemplate/edit/cust_main/contact.html
httemplate/edit/elements/edit.html
httemplate/edit/process/cust_main.cgi
httemplate/edit/process/detach-cust_pkg.html
httemplate/elements/create_uri_query
httemplate/elements/handle_uri_query
httemplate/elements/pager.html
httemplate/elements/searchbar-cust_main.html
httemplate/elements/tr-censustract.html [new file with mode: 0644]
httemplate/elements/tr-cust_main-phones.html [new file with mode: 0644]
httemplate/elements/tr-select-cust_location.html
httemplate/misc/detach_pkg.html
httemplate/misc/disable-msg_template.cgi [new file with mode: 0644]
httemplate/misc/email-customers.html
httemplate/search/cust_bill_pkg.cgi
httemplate/view/cust_main/contacts.html
httemplate/view/cust_main/locations.html
httemplate/view/cust_main/misc.html
httemplate/view/cust_main/packages/location.html

index ca96eb5..4c99c7c 100644 (file)
@@ -236,7 +236,7 @@ tie my %rights, 'Tie::IxHash',
   # customer voiding rights..
   ###
   'Customer payment void rights' => [
-    { rightname=>'Credit card void', desc=>'Enable local-only voiding of echeck payments in addition to refunds against the payment gateway.' }, #aka. cc-void 
+    { rightname=>'Credit card void', desc=>'Enable local-only voiding of credit card payments in addition to refunds against the payment gateway.' }, #aka. cc-void 
     { rightname=>'Echeck void', desc=>'Enable local-only voiding of echeck payments in addition to refunds against the payment gateway.' }, #aka. echeck-void
     'Void payments',
     { rightname=>'Unvoid payments', desc=>'Enable unvoiding of voided payments' }, #aka. unvoid 
index db50d42..2aeecc1 100644 (file)
@@ -2801,13 +2801,16 @@ sub myaccount_passwd {
   } )
     or return { 'error' => "Service not found" };
 
-  if ( exists($p->{'old_password'}) ) {
-    return { 'error' => "Incorrect password." }
-      unless $svc_acct->check_password($p->{'old_password'});
-  }
+  my $error = '';
+
+  my $conf = new FS::Conf;
+  $error = 'Password too short.'
+    if length($p->{'new_password'}) < ($conf->config('passwordmin') || 6);
+  $error = 'Password too long.'
+    if length($p->{'new_password'}) > ($conf->config('passwordmax') || 8);
 
   $svc_acct->set_password($p->{'new_password'});
-  my $error = $svc_acct->replace();
+  $error ||= $svc_acct->replace();
 
   my($label, $value) = $svc_acct->cust_svc->label;
 
index eed84fc..0eed8ee 100644 (file)
@@ -1724,6 +1724,21 @@ and customer address. Include units.',
   },
 
   {
+    'key'         => 'sip_passwordmin',
+    'section'     => 'telephony',
+    'description' => 'Minimum SIP password length (default 6)',
+    'type'        => 'text',
+  },
+
+  {
+    'key'         => 'sip_passwordmax',
+    'section'     => 'telephony',
+    'description' => 'Maximum SIP password length (default 8) (don\'t set this over 12 if you need to import or export crypt() passwords)',
+    'type'        => 'text',
+  },
+
+
+  {
     'key'         => 'password-noampersand',
     'section'     => 'password',
     'description' => 'Disallow ampersands in passwords',
@@ -2040,6 +2055,13 @@ and customer address. Include units.',
   },
 
   {
+    'key'         => 'show_ship_company',
+    'section'     => 'UI',
+    'description' => 'Turns on display/collection of a "service company name" field for customers.',
+    'type'        => 'checkbox',
+  },
+
+  {
     'key'         => 'show_ss',
     'section'     => 'UI',
     'description' => 'Turns on display/collection of social security numbers in the web interface.  Sometimes required by electronic check (ACH) processors.',
@@ -4745,6 +4767,17 @@ and customer address. Include units.',
   },
 
   {
+    'key'         => 'svc_phone-radius-password',
+    'section'     => 'telephony',
+    'description' => 'Password when exporting svc_phone records to RADIUS',
+    'type'        => 'select',
+    'select_hash' => [
+      '' => 'Use default from svc_phone-radius-default_password config',
+      'countrycode_phonenum' => 'Phone number (with country code)',
+    ],
+  },
+
+  {
     'key'         => 'svc_phone-radius-default_password',
     'section'     => 'telephony',
     'description' => 'Default password when exporting svc_phone records to RADIUS',
index 1a88c3a..4937347 100644 (file)
@@ -42,6 +42,7 @@ our $me = '[FS::Record]';
 our $nowarn_identical = 0;
 our $nowarn_classload = 0;
 our $no_update_diff = 0;
+our $no_history = 0;
 
 our $no_check_foreign = 1; #well, not inefficiently in perl by default anymore
 
@@ -1250,7 +1251,7 @@ sub insert {
   }
 
   my $h_sth;
-  if ( defined dbdef->table('h_'. $table) ) {
+  if ( defined( dbdef->table('h_'. $table) ) && ! $no_history ) {
     my $h_statement = $self->_h_statement('insert');
     warn "[debug]$me $h_statement\n" if $DEBUG > 2;
     $h_sth = dbh->prepare($h_statement) or do {
@@ -3003,7 +3004,7 @@ You should generally not have to worry about calling this, as the system handles
 
 sub encrypt {
   my ($self, $value) = @_;
-  my $encrypted;
+  my $encrypted = $value;
 
   if ($conf->exists('encryption')) {
     if ($self->is_encrypted($value)) {
index 1d60b64..da49161 100644 (file)
@@ -757,7 +757,7 @@ sub with_cust_classnum {
     return 'cust_main.classnum in('. join(',',@$classnums) .')'
       if @$classnums;
   }
-  '';
+  ();
 }
 
 
index 8ba6020..bc9d37a 100644 (file)
@@ -3,7 +3,7 @@ package FS::Schema;
 use vars qw(@ISA @EXPORT_OK $DEBUG $setup_hack %dbdef_cache);
 use subs qw(reload_dbdef);
 use Exporter;
-use DBIx::DBSchema 0.43; #0.43 for foreign keys
+use DBIx::DBSchema 0.44; #for foreign keys with MATCH / ON DELETE/UPDATE
 use DBIx::DBSchema::Table;
 use DBIx::DBSchema::Column;
 use DBIx::DBSchema::Index;
@@ -1138,9 +1138,10 @@ sub tables_hashref {
                           { columns    => [ 'invnum' ],
                             table      => 'cust_bill_void',
                           },
-                          { columns    => [ 'pkgnum' ],
-                            table      => 'cust_pkg',
-                          },
+                          #pkgnum 0 and -1 are used for special things
+                          #{ columns    => [ 'pkgnum' ],
+                          #  table      => 'cust_pkg',
+                          #},
                           { columns    => [ 'pkgpart_override' ],
                             table      => 'part_pkg',
                             references => [ 'pkgpart' ],
@@ -2389,7 +2390,7 @@ sub tables_hashref {
       'index'        => [ [ 'billpaynum' ], [ 'billpkgnum' ], ],
       'foreign_keys' => [
                           { columns    => [ 'billpaynum' ],
-                            table      => 'cust_bill_pay_batch',
+                            table      => 'cust_bill_pay',
                           },
                           { columns    => [ 'billpkgnum' ],
                             table      => 'cust_bill_pkg',
@@ -3953,6 +3954,7 @@ sub tables_hashref {
       'foreign_keys' => [
                           { columns    => [ 'jobnum' ],
                             table      => 'queue',
+                            on_delete  => 'CASCADE',
                           },
                         ],
     },
@@ -3973,6 +3975,7 @@ sub tables_hashref {
                           { columns    => [ 'depend_jobnum' ],
                             table      => 'queue',
                             references => [ 'jobnum' ],
+                            on_delete  => 'CASCADE',
                           },
                         ],
     },
@@ -4903,7 +4906,7 @@ sub tables_hashref {
 
         #currently only u4:
         # terminating number (as opposed to dialed destination)
-        'dst_term',    'varchar',  '', $char_d, \"''", '',
+        'dst_term',    'varchar',  'NULL', $char_d, '', '',
 
         #these don't seem to be logged by most of the SQL cdr_* modules
         #except tds under sql-illegal names, so;
@@ -5843,7 +5846,7 @@ sub tables_hashref {
         'statustext', 'varchar', 'NULL', $char_d, '', '',
       ],
       'primary_key' => 'upgradenum',
-      'unique' => [ [ 'upgradenum' ] ],
+      'unique' => [],
       'index' => [ [ 'upgrade' ] ],
     },
 
index 840df75..2314c02 100644 (file)
@@ -581,11 +581,14 @@ sub print_generic {
   my $countrydefault = $conf->config('countrydefault') || 'US';
   foreach ( qw( address1 address2 city state zip country fax) ){
     my $method = 'ship_'.$_;
-    $invoice_data{"ship_$_"} = _latex_escape($cust_main->$method);
+    $invoice_data{"ship_$_"} = $escape_function->($cust_main->$method);
   }
-  foreach ( qw( contact company ) ) { #compatibility
-    $invoice_data{"ship_$_"} = _latex_escape($cust_main->$_);
+  if ( length($cust_main->ship_company) ) {
+    $invoice_data{'ship_company'} = $escape_function->($cust_main->ship_company);
+  } else {
+    $invoice_data{'ship_company'} = $escape_function->($cust_main->company);
   }
+  $invoice_data{'ship_contact'} = $escape_function->($cust_main->contact);
   $invoice_data{'ship_country'} = ''
     if ( $invoice_data{'ship_country'} eq $countrydefault );
   
index d7f998b..e31ff14 100644 (file)
@@ -323,6 +323,14 @@ sub cust_header {
   @cust_header;
 }
 
+sub cust_sort_fields {
+  cust_header(@_);
+  #inefficientish, but tiny lists and only run once per page
+
+  map { $_ eq 'custnum' ? 'custnum' : '' } @cust_fields;
+
+}
+
 =item cust_sql_fields [ CUST_FIELDS_VALUE ]
 
 Returns a list of fields for the SELECT portion of an SQL query.
index b16cb86..bf508dd 100644 (file)
@@ -904,7 +904,7 @@ sub rate_prefix {
       #${$opt{region_group_included_min}} -= $minutes 
       #    if $region_group && $rate_detail->region_group;
 
-      if ( $included_min->{$regionnum}{$ratetimenum} > $minutes ) {
+      if ( $included_min->{$regionnum}{$ratetimenum} >= $minutes ) {
         $charge_sec = 0;
         $included_min->{$regionnum}{$ratetimenum} -= $minutes;
       } else {
index 0eb2166..610753f 100644 (file)
@@ -4,6 +4,7 @@ use base qw( FS::Record );
 use strict;
 use FS::Record qw( qsearch qsearchs );
 use FS::contact;
+use FS::phone_type;
 
 =head1 NAME
 
@@ -144,6 +145,16 @@ sub contact {
   qsearchs( 'contact', { 'contactnum' => $self->contactnum } );
 }
 
+sub phone_type {
+  my $self = shift;
+  qsearchs('phone_type', { 'phonetypenum' => $self->phonetypenum } );
+}
+
+sub typename {
+  my $self = shift;
+  $self->phone_type->typename;
+}
+
 =back
 
 =head1 BUGS
index 4e34ef4..d0e7048 100644 (file)
@@ -1970,14 +1970,15 @@ sub print_csv {
 
   my $time = $opt{'time'} || time;
 
+  my $tracctnum = ''; #leaking out from billco-specific sections :/
   if ( $format eq 'billco' ) {
 
     my $account_num =
       $self->conf->config('billco-account_num', $cust_main->agentnum);
 
-    my $tracctnum = $account_num eq 'display_custnum'
-                      ? $cust_main->display_custnum
-                      : $opt{'tracctnum'};
+    $tracctnum = $account_num eq 'display_custnum'
+                   ? $cust_main->display_custnum
+                   : $opt{'tracctnum'};
 
     my $taxtotal = 0;
     $taxtotal += $_->{'amount'} foreach $self->_items_tax;
@@ -2232,7 +2233,7 @@ sub print_csv {
       $csv->combine(
         '',                     #  1 | N/A-Leave Empty            CHAR   2
         '',                     #  2 | N/A-Leave Empty            CHAR  15
-        $opt{'tracctnum'},      #  3 | Account Number             CHAR  15
+        $tracctnum,             #  3 | Account Number             CHAR  15
         $self->invnum,          #  4 | Invoice Number             CHAR  15
         $lineseq++,             #  5 | Line Sequence (sort order) NUM    6
         $item->{'description'}, #  6 | Transaction Detail         CHAR 100
index d768f84..5126fea 100644 (file)
@@ -1662,7 +1662,7 @@ sub queue_fuzzyfiles_update {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
-  foreach my $field ( 'first', 'last', 'company' ) {
+  foreach my $field ( 'first', 'last', 'company', 'ship_company' ) {
     my $queue = new FS::queue { 
       'job' => 'FS::cust_main::Search::append_fuzzyfiles_fuzzyfield'
     };
@@ -1724,6 +1724,7 @@ sub check {
     || $self->ut_snumbern('spouse_birthdate')
     || $self->ut_snumbern('anniversary_date')
     || $self->ut_textn('company')
+    || $self->ut_textn('ship_company')
     || $self->ut_anything('comments')
     || $self->ut_numbern('referral_custnum')
     || $self->ut_textn('stateid')
@@ -1741,11 +1742,13 @@ sub check {
     || $self->ut_currencyn('currency')
   ;
 
-  my $company = $self->company;
-  $company =~ s/^\s+//; 
-  $company =~ s/\s+$//; 
-  $company =~ s/\s+/ /g;
-  $self->company($company);
+  foreach (qw(company ship_company)) {
+    my $company = $self->get($_);
+    $company =~ s/^\s+//; 
+    $company =~ s/\s+$//; 
+    $company =~ s/\s+/ /g;
+    $self->set($_, $company);
+  }
 
   #barf.  need message catalogs.  i18n.  etc.
   $error .= "Please select an advertising source."
index 16db712..b143861 100644 (file)
@@ -21,6 +21,7 @@ $me = '[FS::cust_main::Search]';
 
 @fuzzyfields = (
   'cust_main.first', 'cust_main.last', 'cust_main.company', 
+  'cust_main.ship_company', # if you're using it
   'cust_location.address1',
   'contact.first',   'contact.last',
 );
@@ -321,6 +322,7 @@ sub smart_search {
     $sql .= " (    LOWER(cust_main.first)         = $q_value
                 OR LOWER(cust_main.last)          = $q_value
                 OR LOWER(cust_main.company)       = $q_value
+                OR LOWER(cust_main.ship_company)  = $q_value
             ";
 
     #address1 (yes, it's a kludge)
@@ -356,27 +358,30 @@ sub smart_search {
 
       #substring
 
-      my @hashrefs = (
+      my @company_hashrefs = (
         { 'company'      => { op=>'ILIKE', value=>"%$value%" }, },
+        { 'ship_company' => { op=>'ILIKE', value=>"%$value%" }, },
       );
 
+      my @hashrefs = ();
+
       if ( $first && $last ) {
 
-        push @hashrefs,
+        @hashrefs = (
           { 'first'        => { op=>'ILIKE', value=>"%$first%" },
             'last'         => { op=>'ILIKE', value=>"%$last%" },
           },
-        ;
+        );
 
       } else {
 
-        push @hashrefs,
+        @hashrefs = (
           { 'first'        => { op=>'ILIKE', value=>"%$value%" }, },
           { 'last'         => { op=>'ILIKE', value=>"%$value%" }, },
-        ;
+        );
       }
 
-      foreach my $hashref ( @hashrefs ) {
+      foreach my $hashref ( @company_hashrefs, @hashrefs ) {
 
         push @cust_main, qsearch( {
           'table'     => 'cust_main',
@@ -402,8 +407,6 @@ sub smart_search {
 
       #contact substring
 
-      shift @hashrefs; #no company column in contact table
-     
       foreach my $hashref ( @hashrefs ) {
 
         push @cust_main,
@@ -439,7 +442,7 @@ sub smart_search {
           %fuzopts
         );
      }
-      foreach my $field ( 'first', 'last', 'company' ) {
+      foreach my $field ( 'first', 'last', 'company', 'ship_company' ) {
         push @cust_main, FS::cust_main::Search->fuzzy_search(
           { $field => $value },
           %fuzopts
@@ -1193,6 +1196,7 @@ sub append_fuzzyfiles {
   #foreach my $fuzzy (@fuzzyfields) {
   foreach my $fuzzy ( 'cust_main.first', 'cust_main.last', 'cust_main.company', 
                       'cust_location.address1',
+                      'cust_main.ship_company',
                     ) {
 
     append_fuzzyfiles_fuzzyfield($fuzzy, shift);
index 0669e1a..a7b9108 100644 (file)
@@ -36,6 +36,7 @@ FS::UID->install_callback( sub {
 } );
 
 @encrypted_fields = ('payinfo');
+sub nohistory_fields { ('payinfo'); }
 
 =head1 NAME
 
index b93d816..db53b19 100644 (file)
@@ -16,6 +16,9 @@ use FS::cust_bill;
 # 3 is even more information including possibly sensitive data
 $DEBUG = 0;
 
+#@encrypted_fields = ('payinfo');
+sub nohistory_fields { ('payinfo'); }
+
 =head1 NAME
 
 FS::cust_pay_batch - Object methods for batch cards
index 8c6ef69..572a2ad 100644 (file)
@@ -12,6 +12,7 @@ use FS::cust_pay;
 @ISA = qw( FS::payinfo_transaction_Mixin FS::cust_main_Mixin FS::Record );
 
 @encrypted_fields = ('payinfo');
+sub nohistory_fields { ('payinfo'); }
 
 =head1 NAME
 
index 92a96cb..c42dc18 100644 (file)
@@ -16,6 +16,8 @@ use FS::cust_pay;
 use FS::cust_pkg;
 
 @encrypted_fields = ('payinfo');
+sub nohistory_fields { ('payinfo'); }
+
 $otaker_upgrade_kludge = 0;
 
 =head1 NAME
index 5abdbe2..8d12ab9 100644 (file)
@@ -1859,7 +1859,7 @@ sub change {
   if ( $opt->{cust_main} ) {
     my $cust_main = $opt->{cust_main};
     unless ( $cust_main->custnum ) { 
-      my $error = $cust_main->insert;
+      my $error = $cust_main->insert( @{ $opt->{cust_main_insert_args}||[] } );
       if ( $error ) {
         $dbh->rollback if $oldAutoCommit;
         return "inserting cust_main (transaction rolled back): $error";
@@ -3461,8 +3461,7 @@ sub attribute_since_sqlradacct {
   foreach my $cust_svc (
     grep {
       my $part_svc = $_->part_svc;
-      $part_svc->svcdb eq 'svc_acct'
-        && scalar($part_svc->part_export_usage);
+      scalar($part_svc->part_export_usage);
     } $self->cust_svc
   ) {
     $sum += $cust_svc->attribute_since_sqlradacct($start, $end, $attrib);
index 0649929..d29db5c 100644 (file)
@@ -13,6 +13,7 @@ use FS::cust_pay_refund;
 use FS::cust_main;
 
 @encrypted_fields = ('payinfo');
+sub nohistory_fields { ('payinfo'); }
 
 =head1 NAME
 
index 57b7b77..835dec2 100644 (file)
@@ -20,6 +20,7 @@ sub option_fields {
                   'type'     => 'select-table',
                   'table'    => 'msg_template',
                   'name_col' => 'msgname',
+                  'hashref'  => { disabled => '' },
                   'disable_empty' => 1,
                 },
   );
index 8e22c68..7c8ed16 100644 (file)
@@ -19,6 +19,7 @@ sub option_fields {
     'msgnum' => { 'label'    => 'Template',
                   'type'     => 'select-table',
                   'table'    => 'msg_template',
+                  'hashref'  => { disabled => '' },
                   'name_col' => 'msgname',
                   'disable_empty' => 1,
                 },
index 194aeb8..d300e33 100644 (file)
@@ -24,6 +24,7 @@ sub option_fields {
                   'type'     => 'select-table',
                   'table'    => 'msg_template',
                   'name_col' => 'msgname',
+                  'hashref'  => { disabled => '' },
                   'disable_empty' => 1,
                 },
   );
index d71a137..97a4ad6 100644 (file)
@@ -18,6 +18,7 @@ sub option_fields {
                   'type'     => 'select-table',
                   'table'    => 'msg_template',
                   'name_col' => 'msgname',
+                  'hashref'  => { disabled => '' },
                   'disable_empty' => 1,
                 },
   );
index 7ae6105..72e387c 100644 (file)
@@ -21,7 +21,7 @@ my %template_select = (
     %templates = (0 => '',
       map { $_->msgnum, $_->msgname } 
       qsearch({ table => 'msg_template',
-                hashref => {},
+                hashref => { disabled => '' },
                 order_by => 'ORDER BY msgnum ASC'
               })
     );
index 6ba131f..1fcb828 100644 (file)
@@ -21,7 +21,7 @@ my %template_select = (
     %templates = (0 => '',
       map { $_->msgnum, $_->msgname } 
       qsearch({ table => 'msg_template',
-                hashref => {},
+                hashref => { disabled => 1 },
                 order_by => 'ORDER BY msgnum ASC'
               })
     );
index ce13695..f7e8651 100644 (file)
@@ -267,17 +267,19 @@ sub export_pkg_change {
 
   my @fields = qw( pkgnum pkgpart agent_pkgid ); #others?
   my @date_fields = qw( order_date start_date setup bill last_bill susp adjourn
-                        resume cancel uncancel expore contract_end );
+                        resume cancel uncancel expire contract_end );
 
   no strict 'vars';
   {
     no strict 'refs';
     foreach (@fields) {
-      ${"old_$_"} = $old_cust_pkg->getfield($_);
+      ${"old_$_"} = $old_cust_pkg ? $old_cust_pkg->getfield($_) : '';
       ${"new_$_"} = $new_cust_pkg->getfield($_);
     }
     foreach (@date_fields) {
-      ${"old_$_"} = time2str('%Y-%m-%d', $old_cust_pkg->getfield($_));
+      ${"old_$_"} = $old_cust_pkg
+                      ? time2str('%Y-%m-%d', $old_cust_pkg->getfield($_))
+                      : '';
       ${"new_$_"} = time2str('%Y-%m-%d', $new_cust_pkg->getfield($_));
     }
   }
index 26550df..4e8850e 100644 (file)
@@ -47,7 +47,7 @@ sub price_info {
 
 #some false laziness-ish w/agent.pm...  not a lot
 sub calc_recur {
-  my($self, $cust_pkg, $sdate, $details ) = @_;
+  my($self, $cust_pkg, $sdate, $details, $param ) = @_;
 
   my $conf = new FS::Conf;
   my $money_char = $conf->config('money_char') || '$';
@@ -106,10 +106,13 @@ sub calc_recur {
     }
   }
 
-  sprintf('%.2f', $self->base_recur($cust_pkg, $sdate) + $total_svc_charge );
-}
+  my $charge = $self->base_recur($cust_pkg, $sdate) + $total_svc_charge;
+
+  $param->{'override_charges'} = $total_svc_charge / $self->freq;
+  my $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param);
 
-sub can_discount { 0; }
+  sprintf('%.2f', $charge - $discount );
+}
 
 sub hide_svc_detail { 1; }
 
index 3f8763d..8ebadd4 100644 (file)
@@ -193,20 +193,6 @@ deleted as well
 sub delete {
   my $self = shift;
 
-  local $SIG{HUP} = 'IGNORE';
-  local $SIG{INT} = 'IGNORE';
-  local $SIG{QUIT} = 'IGNORE';
-  local $SIG{TERM} = 'IGNORE';
-  local $SIG{TSTP} = 'IGNORE';
-  local $SIG{PIPE} = 'IGNORE';
-
-  my $oldAutoCommit = $FS::UID::AutoCommit;
-  local $FS::UID::AutoCommit = 0;
-  my $dbh = dbh;
-
-  my @del = qsearch( 'queue_arg', { 'jobnum' => $self->jobnum } );
-  push @del, qsearch( 'queue_depend', { 'depend_jobnum' => $self->jobnum } );
-
   my $reportname = '';
   if ( $self->status =~/^done/ ) {
     my $dropstring = rooturl(). '/misc/queued_report\?report=';
@@ -216,20 +202,7 @@ sub delete {
   }
 
   my $error = $self->SUPER::delete;
-  if ( $error ) {
-    $dbh->rollback if $oldAutoCommit;
-    return $error;
-  }
-
-  foreach my $del ( @del ) {
-    $error = $del->delete;
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return $error;
-    }
-  }
-
-  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  return $error if $error;
   
   unlink $reportname if $reportname;
 
diff --git a/FS/FS/svc_MAC_Mixin.pm b/FS/FS/svc_MAC_Mixin.pm
new file mode 100644 (file)
index 0000000..737a8e8
--- /dev/null
@@ -0,0 +1,65 @@
+package FS::svc_MAC_Mixin;
+
+use strict;
+#use FS::Record qw(qsearch);
+#use FS::Conf;
+# careful about importing anything here--it will end up in a LOT of 
+# namespaces
+
+#use vars qw(@subclasses $DEBUG $conf);
+
+#$DEBUG = 0;
+
+# any subclass that can have MAC addresses needs to be added here
+#@subclasses = (qw(FS::svc_broadband FS::svc_cable));
+
+#sub conf {
+#  $conf ||= FS::Conf->new;
+#}
+
+=head1 NAME
+
+FS::MAC_Mixin - Mixin class for objects that have MAC addresses assigned.
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=head1 METHODS
+
+=over 4
+
+=item mac_addr_pretty
+
+=cut
+
+sub mac_addr_pretty {
+  my $self = shift;
+  $self->mac_addr_formatted('U',':');
+}
+
+=item mac_addr_formatted CASE DELIMITER
+
+Format the MAC address (for use by exports).  If CASE starts with "l"
+(for "lowercase"), it's returned in lowercase.  DELIMITER is inserted
+between octets.
+
+=cut
+
+sub mac_addr_formatted {
+  my $self = shift;
+  my ($case, $delim) = @_;
+  my $addr = $self->mac_addr;
+  $addr = lc($addr) if $case =~ /^l/i;
+  join( $delim || '', $addr =~ /../g );
+}
+
+=back
+
+=head1 BUGS
+
+=cut
+
+1; 
index 6073902..b9c89ce 100755 (executable)
@@ -3,6 +3,7 @@ use base qw(
   FS::svc_Radius_Mixin
   FS::svc_Tower_Mixin
   FS::svc_IP_Mixin 
+  FS::svc_MAC_Mixin
   FS::svc_Common
   );
 
@@ -262,7 +263,7 @@ sub smart_search {
 
 =item label
 
-Returns the IP address.
+Returns the IP address, MAC address and description.
 
 =cut
 
@@ -419,22 +420,6 @@ sub _check_duplicate {
   '';
 }
 
-=item mac_addr_formatted CASE DELIMITER
-
-Format the MAC address (for use by exports).  If CASE starts with "l"
-(for "lowercase"), it's returned in lowercase.  DELIMITER is inserted
-between octets.
-
-=cut
-
-sub mac_addr_formatted {
-  my $self = shift;
-  my ($case, $delim) = @_;
-  my $addr = $self->mac_addr;
-  $addr = lc($addr) if $case =~ /^l/i;
-  join( $delim || '', $addr =~ /../g );
-}
-
 #class method
 sub _upgrade_data {
   my $class = shift;
index 672a34d..5d28113 100644 (file)
@@ -1,5 +1,7 @@
 package FS::svc_cable;
-use base qw( FS::svc_Common ); #qw( FS::device_Common FS::svc_Common );
+use base qw( FS::svc_MAC_Mixin
+             FS::svc_Common
+           ); #FS::device_Common
 
 use strict;
 use Tie::IxHash;
@@ -115,6 +117,22 @@ sub table_info {
   };
 }
 
+=item label
+
+Returns the MAC address and serial number.
+
+=cut
+
+sub label {
+  my $self = shift;
+  my @label = ();
+  push @label, 'MAC:'. $self->mac_addr_pretty
+    if $self->mac_addr;
+  push @label, 'Serial#'. $self->serialnum
+    if $self->serialnum;
+  return join(', ', @label);
+}
+
 =item insert
 
 Adds this record to the database.  If there is an error, returns the error,
index 77b0137..887f7be 100644 (file)
@@ -2,9 +2,12 @@ package FS::svc_phone;
 
 use strict;
 use base qw( FS::svc_Domain_Mixin FS::location_Mixin FS::svc_Common );
-use vars qw( $DEBUG $me @pw_set $conf $phone_name_max );
+use vars qw( $DEBUG $me @pw_set $conf $phone_name_max
+             $passwordmin $passwordmax
+           );
 use Data::Dumper;
 use Scalar::Util qw( blessed );
+use List::Util qw( min );
 use FS::Conf;
 use FS::Record qw( qsearch qsearchs dbh );
 use FS::PagedSearch qw( psearch );
@@ -26,6 +29,8 @@ $DEBUG = 0;
 FS::UID->install_callback( sub { 
   $conf = new FS::Conf;
   $phone_name_max = $conf->config('svc_phone-phone_name-max_length');
+  $passwordmin = $conf->config('sip_passwordmin') || 0;
+  $passwordmax = $conf->config('sip_passwordmax') || 80;
 }
 );
 
@@ -534,10 +539,17 @@ sub check {
     }
   }
 
-  unless ( length($self->sip_password) ) { # option for this?
+  if ( length($self->sip_password) ) {
+
+    return "SIP password must be longer than $passwordmin characters"
+      if length($self->sip_password) < $passwordmin;
+    return "SIP password must be shorter than $passwordmax characters"
+      if length($self->sip_password) > $passwordmax;
+
+  } else { # option for this?
 
     $self->sip_password(
-      join('', map $pw_set[ int(rand $#pw_set) ], (0..16) )
+      join('', map $pw_set[ int(rand $#pw_set) ], (1..min($passwordmax,16)) )
     );
 
   }
@@ -638,7 +650,13 @@ sub radius_check {
 
   my $conf = new FS::Conf;
 
-  $check{'User-Password'} = $conf->config('svc_phone-radius-default_password');
+  my $password;
+  if ( $conf->config('svc_phone-radius-password') eq 'countrycode_phonenum' ) {
+    $password = $self->countrycode. $self->phonenum;
+  } else {
+    $password = $conf->config('svc_phone-radius-default_password');
+  }
+  $check{'User-Password'} = $password;
 
   %check;
 }
index 5eac06b..f1a87ca 100644 (file)
@@ -12,6 +12,7 @@ use FS::Record qw(qsearch);
 use FS::queue;
 use FS::queue_depend;
 use FS::Log;
+use FS::Cron::expire_user_pref qw( expire_user_pref );
 
 # no autoloading for non-FS classes...
 use Net::SSH 0.07;
@@ -66,6 +67,7 @@ while (1) {
   if ( $kids >= $max_kids ) {
     warn "WARNING: maximum $kids children reached\n" unless $warnkids++;
     &reap_kids;
+    expire_user_pref() unless $warnkids % 10;
     sleep 1; #waiting for signals is cheap
     next;
   }
@@ -131,6 +133,7 @@ while (1) {
       undef $FS::UID::dbh;
       next;
     };
+    expire_user_pref();
     sleep $sleep_time;
     next;
   }
index 7cacf10..45d2709 100755 (executable)
@@ -84,6 +84,13 @@ if ( dbdef->table('areacode') and
   }
 }
 
+if ( dbdef->table('upgrade_journal') ) {
+  push @bugfix, "SELECT SETVAL( 'upgrade_journal_upgradenum_seq',
+                                ( SELECT MAX(upgradenum) FROM upgrade_journal )
+                              )
+                ";
+}
+
 if ( $DRY_RUN ) {
   print
     join(";\n", @bugfix ). ";\n";
diff --git a/bin/restore-ship_company b/bin/restore-ship_company
new file mode 100644 (file)
index 0000000..cee7009
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/perl
+
+use FS::UID 'adminsuidsetup';
+use FS::Record qw(qsearch qsearchs dbh);
+use FS::cust_main;
+my $user = shift or die "Usage: 
+  restore-ship_company username [ max-age ]
+";
+adminsuidsetup($user);
+
+$FS::UID::AutoCommit = 1;
+local $FS::cust_main::import = 1;
+
+my $days = shift || 30;
+my $time = time - (86400*$days); # by default, only restore within the last
+                                 # 30 days
+foreach my $cust_main (qsearch('cust_main', { ship_company => '' })) {
+  my $custnum = $cust_main->custnum;
+  my $last_h = qsearchs({
+      table     => 'h_cust_main',
+      extra_sql => " WHERE custnum = $custnum".
+                   " AND ship_company IS NOT NULL".
+                   " AND history_date >= $time",
+      order_by  => " ORDER BY history_date DESC LIMIT 1",
+  });
+  next if !$last_h;
+  print "$custnum\t".$last_h->ship_company."\n";
+  $cust_main->set('ship_company' => $last_h->ship_company);
+  my $error = $cust_main->replace;
+  warn "Error setting service company for customer #$custnum:\n  $error\n"
+    if $error;
+}
index 45a257a..e39a28a 100644 (file)
@@ -3,6 +3,7 @@
 #This drops anything from the database that could cause live things to happen.
 #You'd want to do this on a test copy of your live database but NEVER on the
 #live database itself.
+die "remove this line to run -- NEVER ON A LIVE DATABASE";
 
 #-all exports (all records in part_export, part_export_option export_svc)
 #-all non-POST invoice destinations (cust_main_invoice)
@@ -42,6 +43,17 @@ my $dsth = dbh->prepare("DELETE FROM cust_main_invoice WHERE dest != 'POST'")
   or die dbh->errstr;
 $dsth->execute or die $dsth->errstr;
 
+foreach my $table (qw( cust_main
+                       cust_pay_pending cust_pay cust_pay_void cust_pay_batch
+                       cust_refund
+)) {
+  my $ccsth = dbh->prepare("
+    UPDATE $table SET payinfo = '4111111111111111'
+      WHERE payby = 'CARD' OR payby = 'DCRD'
+  ") or die dbh->errstr;
+  $ccsth->execute or die $ccsth->errstr;
+}
+
 my $sth = dbh->prepare("UPDATE part_event SET disabled = 'Y'");
 $sth->execute or die $sth->errstr;
 
index 0df24f7..6086717 100644 (file)
@@ -1,8 +1,13 @@
 #Add this to the modules section of radiusd.conf
 # perl {
 #   #path to this module
-#   module=/usr/local/share/perl/5.8.8/FS/SelfService/FreeRadiusVoip.pm
+#   # deb 6 example
+#   #module=/usr/local/share/perl/5.10.1/FS/SelfService/FreeRadiusVoip.pm
+#   # deb 7 example
+#   module=/usr/local/share/perl/5.14.2/FS/SelfService/FreeRadiusVoip.pm
+#
 #   func_authorize = authorize;
+#
 # }
 #
 #In the Authorize section 
@@ -11,9 +16,9 @@
 #
 # #N/A# Add a line containing 'perl' to the Accounting section. 
 # 
-# and on debian systems, add this to /etc/init.d/freeradius, with the
+# and on debian systems, add this to /etc/init.d/freeradius, with the
 # correct path (http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=416266)
-#               LD_PRELOAD=/usr/lib/libperl.so.5.8.8
+#               LD_PRELOAD=/usr/lib/libperl.so.5.10
 #               export LD_PRELOAD
 
 BEGIN { $FS::SelfService::skip_uid_check = 1; } 
@@ -44,9 +49,13 @@ sub authorize {
   if ( $response->{'error'} ) {
     $RAD_REPLY{'Reply-Message'} = $response->{'error'};
     return RLM_MODULE_REJECT;
-  } else {
+  } elsif ( $response->{'seconds'} ) {
     $RAD_REPLY{'Session-Timeout'} = $response->{'seconds'};
     return RLM_MODULE_OK;
+  } else {
+    # if the called number is free, put 1 in the Termination-Action attribute
+    $RAD_REPLY{'Termination-Action'} = 1;
+    return RLM_MODULE_OK;
   }
 
 }
index bb5ac94..ef0b2da 100644 (file)
@@ -5,14 +5,15 @@
               'query'         => { 'table' => 'msg_template', },
               'count_query'   => 'SELECT COUNT(*) FROM msg_template',
               'disableable'   => 1,
-              'disabled_statuspos' => 2,
+              'disabled_statuspos' => (scalar(@locales) + 3),
               'agent_virt'         => 1,
               'agent_null_right'   => ['View global templates','Edit global templates'],
               'agent_pos'          => 1,
-              'header'     => [ 'Name', '', map '', @locales ],
-              'fields'     => [ 'msgname', @locales ],
-              'links'      => [ $link, @locale_links ],
-              'cell_style' => [ '', '', map $locale_style, @locales ],
+              'header'      => [ 'Name', '', map ('', @locales), '' ],
+              'fields'      => [ 'msgname', @locales, $disable_link_label ],
+              'links'       => [ $link, @locale_links, '' ],
+              'link_onclicks' => [ '', map('', @locale_links), $disable_link ],
+              'cell_style'    => [ '', '', map ($locale_style, @locales), $locale_style ],
           )
 %>
 <%init>
@@ -30,7 +31,7 @@ if ( $curuser->access_right(['Edit templates', 'Edit global templates']) ) {
 
 my $link = [ "${p}edit/msg_template.html?msgnum=", 'msgnum' ];
 
-my $locale_style = 'font-size:0.8em; padding:3px; background-color:';
+my $locale_style = 'font-size:0.8em; padding:3px';
 
 my (@locales, @locale_links);
 foreach my $l ( FS::Locales->locales ) {
@@ -44,6 +45,20 @@ foreach my $l ( FS::Locales->locales ) {
     [ "${p}edit/msg_template.html?locale=$l;msgnum=", 'msgnum' ];
   };
 }
-    
+
+my $disable_link = sub {
+  my $template = shift;
+  include('/elements/popup_link_onclick.html',
+    action      => $p.'misc/disable-msg_template.cgi?msgnum=' .
+                     $template->msgnum .
+                     ($template->disabled ? ';enable=1' : ''),
+    actionlabel => 'Disable lemplate',
+  );
+};
+
+my $disable_link_label = sub {
+  my $template = shift;
+  $template->disabled ? '(enable)' : '(disable)' ;
+};
 
 </%init>
index d597d0b..480047c 100755 (executable)
@@ -73,6 +73,7 @@
     ><% mt('same as billing address') |h %>
     <DIV CLASS="fsinnerbox">
       <TABLE ID="table_ship_location" WIDTH="100%">
+      <& cust_main/before_ship_location.html, $cust_main &>
       <& /elements/location.html,
           object => $cust_main->ship_location,
           prefix => 'ship_',
@@ -202,6 +203,7 @@ my $prospectnum = '';
 my $locationnum = '';
 my $same = '';
 
+$m->comp('/elements/handle_uri_query', 'secure'=>1);
 
 if ( $cgi->param('error') ) {
 
diff --git a/httemplate/edit/cust_main/before_ship_location.html b/httemplate/edit/cust_main/before_ship_location.html
new file mode 100644 (file)
index 0000000..badb5e8
--- /dev/null
@@ -0,0 +1,13 @@
+% if ( length($cust_main->ship_company) or
+%      $conf->exists('show_ship_company') ) {
+  <& /elements/tr-input-text.html,
+      label       => mt('Company'),
+      field       => 'ship_company',
+      curr_value  => $cust_main->ship_company,
+      colspan     => 6,
+  &>
+% }
+<%init>
+my $cust_main = shift;
+my $conf = FS::Conf->new;
+</%init>
index 4140ec1..c2ebb09 100644 (file)
 </%def>
 
 <%def phones>
-  <TR>
-    <TD ALIGN="right" VALIGN="top"><% mt('Phones') %></TD>
-    <TD COLSPAN=6>
-
-      <TABLE CELLSPACING=0 CELLPADDING=0>
-        <TR>
-          <TD>
-            <INPUT TYPE="text" NAME="<%$pre%>daytime" VALUE="<% $cust_main->get($pre.'daytime') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
-            <BR><FONT SIZE=-1><% $daytime_label %></FONT>
-          </TD>
-          <TD>&nbsp;</TD>
-          <TD>
-            <INPUT TYPE="text" NAME="<%$pre%>night" VALUE="<% $cust_main->get($pre.'night') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
-            <BR><FONT SIZE=-1><% $night_label %></FONT>
-          </TD>
-          <TD>&nbsp;</TD>
-          <TD>
-            <INPUT TYPE="text" NAME="<%$pre%>mobile" VALUE="<% $cust_main->get($pre.'mobile') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
-            <BR><FONT SIZE=-1><% $mobile_label %></FONT>
-          </TD>
-        </TR>
-      </TABLE>
-    </TD>
-  </TR>
+  <& /elements/tr-cust_main-phones.html,
+       'prefix'       => $pre,
+       'cust_main'    => $cust_main,
+       'onchange'     => $onchange,
+       'disabled'     => $disabled,
+       'style'        => $style,
+  &>
 </%def>
 
 <%def fax>
@@ -136,9 +119,7 @@ my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
 </%once>
 <%shared>
 
-my( %opt, $cust_main, $pre, $onchange, $disabled, @style, $style,
-    $daytime_label, $night_label, $mobile_label,
-  );
+my( %opt, $cust_main, $pre, $onchange, $disabled, @style, $style );
 
 </%shared>
 <%init>
@@ -176,16 +157,6 @@ $opt{geocode} ||= $cust_main->get('geocode');
 
 $opt{censustract} ||= $cust_main->censustract;
 
-$daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/
-                   ? 'Day'
-                   : FS::Msgcat::_gettext('daytime');
-$night_label   = FS::Msgcat::_gettext('night') =~/^(night)?$/
-                   ? 'Night'
-                   : FS::Msgcat::_gettext('night') || 'Night';
-$mobile_label = FS::Msgcat::_gettext('mobile') =~/^(mobile)?$/
-                   ? 'Mobile'
-                   : FS::Msgcat::_gettext('mobile') || 'Mobile';
-
 my $stateid_label = FS::Msgcat::_gettext('stateid') =~ /^(stateid)?$/
                   ? 'Driver&rsquo;s License'
                   : FS::Msgcat::_gettext('stateid') || 'Driver&rsquo;s License';
index 6c96532..9e27f2a 100644 (file)
@@ -527,7 +527,7 @@ Example:
 %     if ( $f->{curr_value_callback} ) {
 %       $curr_value = &{ $f->{curr_value_callback} }( $cgi, $object, $field ),
 %     } else {
-%       $curr_value = $object->$field();
+%       $curr_value = $object->$field() if $field;
 %     }
 %     $curr_value = &{ $opt{'value_callback'} }( $f->{'field'}, $curr_value )
 %       if $opt{'value_callback'} && $mode ne 'error';
index ff8be1a..4fb8f62 100755 (executable)
@@ -1,7 +1,7 @@
 % if ( $error ) {
 %   $cgi->param('error', $error);
-%
-<% $cgi->redirect(popurl(2). "cust_main.cgi?". $cgi->query_string ) %>
+%   my $query = $m->scomp('/elements/create_uri_query', 'secure'=>1);
+<% $cgi->redirect(popurl(2). "cust_main.cgi?$query" ) %>
 %
 % } else { 
 %
index ab87eb5..782ffa5 100644 (file)
@@ -30,16 +30,23 @@ my $cust_location = new FS::cust_location {
   map { $_ => scalar($cgi->param($_)) } FS::cust_main->location_fields
 };
 
+#false laziness w/process/cust_main.cgi
+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 $cust_main = new FS::cust_main {
   ( map { ( $_, scalar($cgi->param($_)) ) } fields('cust_main') ),
   ( map { ( "ship_$_", '' ) } FS::cust_main->location_fields ),
-  'bill_location' => $cust_location,
-  'ship_location' => $cust_location,
+  'bill_location'  => $cust_location,
+  'ship_location'  => $cust_location,
 };
 
 my $pkg_or_error = $cust_pkg->change( {
-  'keep_dates' => 1,
-  'cust_main'  => $cust_main,
+  'keep_dates'            => 1,
+  'cust_main'             => $cust_main,
+  'cust_main_insert_args' => [ {}, \@invoicing_list ],
 } );
 
 my $error = ref($pkg_or_error) ? '' : $pkg_or_error;
index 32d8e2f..ce6249e 100644 (file)
@@ -1,17 +1,34 @@
 <% $query %>\
 <%init>
 
+my %opt = @_;
+
+if ( $opt{secure} ) {
+
+  foreach my $param (grep /pay(info\d?|cvv)$/, $cgi->param) {
+    my $value = $cgi->param($param);
+    next unless length($value);
+    my $encrypted = FS::Record->encrypt( $value );
+    $cgi->param($param, $encrypted);
+  }
+
+}
+
 my $query = $cgi->query_string;
 
-if ( length($query) > 1920 ) { #stupid IE 2083 URL limit
+if ( length($query) > 1920 || $opt{secure} ) { #stupid IE 2083 URL limit
 
   my $session = int(rand(4294967296)); #XXX
   my $pref = new FS::access_user_pref({
     'usernum'    => $FS::CurrentUser::CurrentUser->usernum,
     'prefname'   => "redirect$session",
     'prefvalue'  => $query,
-    'expiration' => time + 3600, #1h?  1m?
+    'expiration' => time + ( $opt{secure} ? 120  #2m?
+                                          : 3600 #1h?
+                           ),
   });
+  local($FS::Record::no_history) = 1;
+
   my $pref_error = $pref->insert;
   if ( $pref_error ) {
     die "FATAL: couldn't even set redirect cookie: $pref_error".
index eb7ea1a..2dea96a 100644 (file)
@@ -1,8 +1,20 @@
 <%init>
+
+my %opt = @_;
+
 if ( $cgi->param('redirect') ) {
   my $session = $cgi->param('redirect');
+
   my $pref = $FS::CurrentUser::CurrentUser->option("redirect$session");
   die "unknown redirect session $session\n" unless length($pref);
   $cgi = new CGI($pref);
+
+  foreach my $param (grep /pay(info\d?|cvv)$/, $cgi->param) {
+    my $value = $cgi->param($param);
+    next unless length($value);
+    my $decrypted = FS::Record->decrypt( $value );
+    $cgi->param($param, $decrypted);
+  }
+
 }
 </%init>
index a53300f..d360e64 100644 (file)
@@ -1,13 +1,8 @@
-% my %opt = @_;
-% my $pager = '';
-%
 % if ( $opt{'total'} != $opt{'num_rows'} && $opt{'maxrecords'} ) {
 %
 %   unless ( $opt{'offset'} == 0 ) {
 %     $cgi->param('offset', $opt{'offset'} - $opt{'maxrecords'});
-
       <A HREF="<% $cgi->self_url %>"><B><FONT SIZE="+1">Previous</FONT></B></A>
-
 %   }
 %
 %   my $page = 0;
 %
 %   unless ( $opt{'offset'} + $opt{'maxrecords'} > $opt{'total'} ) {
 %     $cgi->param('offset', $opt{'offset'} + $opt{'maxrecords'});
-
       <A HREF="<% $cgi->self_url %>"><B><FONT SIZE="+1">Next</FONT></B></A>
-%
 %   }
 %
+%   $cgi->param('offset', $orig_offset); #so future $self_url invocations don't advance a page
+%
 % }
+<%init>
+
+my %opt = @_;
+
+my $orig_offset = $opt{'offset'};
+
+</%init>
+
index 9a98417..5bfef48 100644 (file)
@@ -1,6 +1,6 @@
 % if ( $curuser->access_right('List customers') ) {
 
-  <FORM ACTION="<%$fsurl%>search/cust_main.cgi" METHOD="GET" STYLE="margin:0">
+  <FORM ACTION="<%$fsurl%>search/cust_main.cgi" METHOD="POST" STYLE="margin:0">
     <INPUT NAME="search_cust" TYPE="text" VALUE="<% $cust_label |n %>" STYLE="width:<%$width%>" onFocus="clearhint_search_cust(this);" onClick="clearhint_search_cust(this);" CLASS="fstext"><BR>
     <A HREF="<%$fsurl%>search/report_cust_main.html" CLASS="fslink" STYLE="font-size: 11px"><% mt('Advanced') |h %></A>
     <INPUT TYPE="submit" VALUE="<% mt('Search customers') |h %>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px">
diff --git a/httemplate/elements/tr-censustract.html b/httemplate/elements/tr-censustract.html
new file mode 100644 (file)
index 0000000..bd014f1
--- /dev/null
@@ -0,0 +1,23 @@
+% if ($censustract) {
+<TR>
+  <TD ALIGN="right"><% mt('Census tract') |h %></TD>
+  <TD COLSPAN=5>
+    <SPAN STYLE="background-color: #ffffff; border: 1px solid #ffffff"><% $censustract |h %></SPAN>
+    &nbsp;<% $censusyear |h %>
+  </TD>
+</TR>
+% }
+<%init>
+
+my $location = shift;
+my $conf = FS::Conf->new;
+my ($censustract, $censusyear);
+if ($location->censustract) {
+  $censustract = $location->censustract;
+  $censusyear = '('. ($location->censusyear || mt('unknown year')) . ')';
+} elsif ($conf->exists('cust_main-require_censustract')) {
+  $censustract = mt('unknown');
+  $censusyear = '';
+}
+
+</%init>
diff --git a/httemplate/elements/tr-cust_main-phones.html b/httemplate/elements/tr-cust_main-phones.html
new file mode 100644 (file)
index 0000000..accf8ac
--- /dev/null
@@ -0,0 +1,45 @@
+  <TR>
+    <TD ALIGN="right" VALIGN="top"><% mt('Phones') %></TD>
+    <TD COLSPAN=6>
+
+      <TABLE CELLSPACING=0 CELLPADDING=0>
+        <TR>
+          <TD>
+            <INPUT TYPE="text" NAME="<%$pre%>daytime" VALUE="<% $cust_main->get($pre.'daytime') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
+            <BR><FONT SIZE=-1><% $daytime_label %></FONT>
+          </TD>
+          <TD>&nbsp;</TD>
+          <TD>
+            <INPUT TYPE="text" NAME="<%$pre%>night" VALUE="<% $cust_main->get($pre.'night') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
+            <BR><FONT SIZE=-1><% $night_label %></FONT>
+          </TD>
+          <TD>&nbsp;</TD>
+          <TD>
+            <INPUT TYPE="text" NAME="<%$pre%>mobile" VALUE="<% $cust_main->get($pre.'mobile') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
+            <BR><FONT SIZE=-1><% $mobile_label %></FONT>
+          </TD>
+        </TR>
+      </TABLE>
+    </TD>
+  </TR>
+<%init>
+
+my $daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/
+                      ? 'Day'
+                      : FS::Msgcat::_gettext('daytime');
+my $night_label   = FS::Msgcat::_gettext('night') =~/^(night)?$/
+                      ? 'Night'
+                      : FS::Msgcat::_gettext('night') || 'Night';
+my $mobile_label = FS::Msgcat::_gettext('mobile') =~/^(mobile)?$/
+                      ? 'Mobile'
+                      : FS::Msgcat::_gettext('mobile') || 'Mobile';
+
+my %opt = @_;
+
+my $pre       = $opt{'prefix'};
+my $cust_main = $opt{'cust_main'};
+my $onchange  = $opt{'onchange'};
+my $disabled  = $opt{'disabled'};
+my $style     = $opt{'style'};
+
+</%init>
index e1fa825..4ed9cd4 100644 (file)
@@ -31,6 +31,9 @@ Example:
       else what.form.<%$_%>.value = '';
       if( ftype != 'SELECT') what.form.<%$_%>.style.backgroundColor = '#dddddd';
 %   } 
+    if(what.form.enter_censustract) {
+      what.form.enter_censustract.disabled = true;
+    }
   }
 
   function location_clear(what) {
@@ -38,6 +41,9 @@ Example:
       var ftype = what.form.<%$_%>.tagName;
       if( ftype == 'INPUT' ) what.form.<%$_%>.value = '';
 %   }
+    if(what.form.enter_censustract) {
+      what.form.enter_censustract.value = '';
+    }
 %   if ( $opt{'alt_format'} ) {
       changeSelect(what.form.location_kind, '');
       changeSelect(what.form.location_type, '');
@@ -51,6 +57,9 @@ Example:
       var ftype = what.form.<%$_%>.tagName;
       if( ftype != 'SELECT') what.form.<%$_%>.style.backgroundColor = '#ffffff';
 %   } 
+    if(what.form.enter_censustract) {
+      what.form.enter_censustract.disabled = false;
+    }
 %   if ( $opt{'alt_format'} ) {
       if ( what.form.location_type &&
            what.form.location_type.options[what.form.location_type.selectedIndex].value ) {
index 64b3e6e..366bbac 100755 (executable)
     </TD>
   </TR>
 
+%  if ( $conf->config_bool('cust_main-require_phone') ) {
+%    #XXX should be sticky on errors
+%    my $ph_cust_main = FS::cust_main->new({});
+%    foreach my $contact_phone ( $cust_contact->contact_phone ) {
+%      my $t = $contact_phone->typename;
+%      #countrycodes?  interface doesn't parse/take em yet
+%      $ph_cust_main->daytime( $contact_phone->phonenum ) if $t eq 'Work';
+%      $ph_cust_main->night(   $contact_phone->phonenum ) if $t eq 'Home';
+%      $ph_cust_main->mobile(  $contact_phone->phonenum ) if $t eq 'Mobile';
+%      $ph_cust_main->fax(     $contact_phone->phonenum ) if $t eq 'Fax';
+%    }
+
+     <& /elements/tr-cust_main-phones.html,
+          'cust_main'    => $ph_cust_main,
+     &>
+%  }
+
 </TABLE>
 
-%#XXX payment info
+%#payment info
 %#XXX should be sticky on errors...
 <& /edit/cust_main/billing.html, FS::cust_main->new({}),
                                  invoicing_list => [],
diff --git a/httemplate/misc/disable-msg_template.cgi b/httemplate/misc/disable-msg_template.cgi
new file mode 100644 (file)
index 0000000..1eb4d25
--- /dev/null
@@ -0,0 +1,77 @@
+% if ( @error ) {
+<& /elements/errorpage-popup.html, @error &>
+% } else {
+<& /elements/header-popup.html, "Template ${actioned}" &>
+  <SCRIPT TYPE="text/javascript">
+    window.top.location.reload();
+  </SCRIPT>
+</BODY>
+</HTML>
+% }
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $conf = FS::Conf->new;
+my @error;
+my $actioned;
+
+die "access denied"
+  unless $curuser->access_right([ 'Edit templates', 'Edit global templates' ]);
+
+my $msgnum = $cgi->param('msgnum');
+$msgnum =~ /^\d+$/ or die "bad msgnum '$msgnum'";
+my $msg_template = qsearchs({
+  table     => 'msg_template',
+  hashref   => { msgnum => $msgnum },
+  extra_sql => ' AND '.
+    $curuser->agentnums_sql(null_right => 'Edit global templates'),
+});
+die "unknown msgnum $msgnum" unless $msg_template;
+
+if ( $cgi->param('enable') ) {
+  $actioned = 'enabled';
+  $msg_template->set('disabled' => '');
+} else {
+  $actioned = 'disabled';
+  # make sure it's not in use anywhere
+  my @inuse;
+
+  # notice, letter, notice_to events (if they're enabled)
+  my @events = qsearch({
+    table     => 'part_event_option',
+    addl_from => ' JOIN part_event USING (eventpart)',
+    hashref   => {
+      optionname => 'msgnum',
+      optionvalue => $msgnum,
+    },
+    extra_sql => ' AND disabled IS NULL',
+  });
+  push @inuse, map {"Billing event #".$_->eventpart} @events;
+
+  # send_email and rt_ticket exports
+  my @exports = qsearch( 'part_export_option', {
+    optionname => { op => 'LIKE', value => '%_template' },
+    optionvalue => $msgnum,
+  });
+  push @inuse, map {"Export #".$_->exportnum} @exports;
+
+  # payment_receipt_msgnum, decline_msgnum, etc.
+  my @confs = qsearch( 'conf', {
+    name => { op => 'LIKE', value => '%_msgnum' },
+    value => $msgnum,
+  });
+  push @inuse, map {"Configuration setting ".$_->name} @confs;
+  # XXX pending queue jobs?
+  if (@inuse) {
+    @error = ("This template is in use.  Check the following settings:",
+              @inuse);
+  }
+
+  # good to go
+  $msg_template->set(disabled => 'Y');
+}
+if (!@error) {
+  my $error = $msg_template->replace;
+  push @error, $error if $error;
+}
+</%init>
index ad67b8d..3b2ac3c 100644 (file)
@@ -98,6 +98,7 @@ Template:
     <% include('/elements/select-table.html',
                   'label'         => 'Template:',
                   'table'         => 'msg_template',
+                  'hashref'       => { disabled => '' },
                   'name_col'      => 'msgname',
                   'empty_label'   => '(none)',
                   'onchange'      => 'toggle(this)',
index d86641d..f87862a 100644 (file)
@@ -49,8 +49,9 @@
                    @currency,
                    'invnum',
                    '_date',
-                   #'pay_amount',
-                   #'credit_amount',
+                   '', #'pay_amount',
+                   '', #'credit_amount',
+                   FS::UI::Web::cust_sort_fields(),
                  ],
                  'links'       => [
                    @pkgnum_null,
index d65af66..294b7ba 100644 (file)
     <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->company |h %></TD>
   </TR>
 %   }
-% } # if $this eq 'bill'
+% } elsif ( $this eq 'ship' ) {
+%   if ( $cust_main->ship_company ) { # mostly obsolete these days...
+  <TR>
+    <TD ALIGN="right"><% mt('Company') |h %></TD>
+    <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->ship_company |h %></TD>
+  </TR>
+%   }
+% }
 % # now the actual address
 <TR>
   <TD ALIGN="right"><% mt('Address') |h %></TD>
@@ -84,6 +91,7 @@
                                $cust_main->agentnum,
   &>
 % }
+<& /elements/tr-censustract.html, $location &>
   
 % if ( $this eq 'bill' ) {
 %   # billing contact phone numbers
index 7eb52ca..fdbbc39 100755 (executable)
@@ -1,40 +1,60 @@
 <STYLE>
-span.loclabel {
+div.loclabel {
+  display: inline-block;
   padding-left: 4px; 
   padding-right: 4px; 
   background-color: #cccccc;
-  border: 1px solid black
+  border: 1px solid black;
+  border-bottom: 0px;
+  border-radius: 4px 4px 0 0; 
+}
+div.disabled {
+  font-style: italic;
+  color: #808080;
 }
 table.location {
   width: 100%;
   padding: 1px;
   border-spacing: 0px;
 }
+.location-head th {
+  padding-bottom: 0px; 
+  padding-left: 0px; 
+  border-bottom: 1px solid black;
+  vertical-align: bottom;
+  text-align: left;
+  width: 100%;
+}
 </STYLE>
 % foreach my $locationnum (@sorted) {
 %   my $packages = $packages_in{$locationnum};
 %   my $loc = $locations{$locationnum};
 %   next if $loc->disabled and scalar(@$packages) == 0;
 <TABLE CLASS="grid location">
-<TR><TH COLSPAN=3 ALIGN="left" VALIGN="bottom" 
-STYLE="padding-bottom: 0px; 
-  padding-left: 0px; 
-  border-bottom-style: solid;
-  border-bottom-color: black;
-  border-bottom-width: 1px;">
-<SPAN CLASS="loclabel">
-%   if ( $loc->disabled ) {
-<FONT COLOR="#808080"><I>
+<TR CLASS="location-head">
+<TH COLSPAN=5>
+<DIV CLASS="<% $loc->disabled ? 'loclabel disabled' : 'loclabel' %>">
+<% $loc->location_label %>
+%   if ( $loc->censustract ) {
+        <BR>
+        <FONT SIZE=-1>
+        <% $loc->censustract %> (<% $loc->censusyear %> census)
+        </FONT>
+%   } elsif ( $conf->exists('cust_main-require_censustract') ) {
+        <BR>
+        <FONT SIZE=-1 COLOR="#ee3300">
+        <% emt('Census tract unknown') %>
+        </FONT>
 %   }
-<% $loc->location_label %></SPAN>
-<SPAN STYLE="float:right;">
+</DIV>
+<DIV STYLE="display: inline; float:right;">
 % if ( $locationnum && !$loc->disabled && ! $opt{no_links} ) {
 <% edit_location_link($locationnum) %>
 % }
 % if ( $locationnum && !$loc->disabled && !$active{$locationnum} && ! $opt{no_links} ) {
 &nbsp;<% disable_location_link($locationnum) %>
 % }
-</SPAN></TH></TR>
+</DIV></TH></TR>
 %   if (@$packages) {
       <& packages/section.html,
            'packages'  => $packages,
@@ -48,6 +68,7 @@ STYLE="padding-bottom: 0px;
 my %opt = @_;
 my $cust_main = $opt{'cust_main'};
 my $all_packages = $opt{'packages'};
+my $conf = FS::Conf->new;
 
 my %locations = map { $_->locationnum => $_ } qsearch({
     'table'     => 'cust_location',
index 5311aa5..7915195 100644 (file)
 
 % }
 
-% if ( $conf->exists('cust_main-require_censustract') ) {
-
-  <TR>
-    <TD ALIGN="right">
-      <% mt('Census tract ([_1])', $cust_main->ship_location->censusyear) |h %>
-    </TD>
-    <TD BGCOLOR="#ffffff"><% $cust_main->ship_location->censustract  %></TD>
-  </TR>
-
-% }
-
 % if ( $cust_main->district ) {
 
   <TR>
index 5ff2b1e..db67d45 100644 (file)
@@ -1,35 +1,49 @@
 % if ( $cust_pkg->change_from_pkg
-%      and $cust_pkg->change_from_pkg->locationnum == $cust_pkg->locationnum )
+%      and $cust_pkg->change_from_pkg->locationnum == $cust_pkg->locationnum)
 % {
 % # don't show the location
 % } else {
-%   if ( $default ) {
-    <DIV STYLE="font-style: italic; font-size: small">
-%   }
+%   if ( !$conf->exists('cust_pkg-group_by_location') ) {
+%     if ( $default ) {
+        <DIV STYLE="font-style: italic; font-size: small">
+%     }
 
-    <% $loc->location_label( 'join_string'     => '<BR>',
-                             'double_space'    => ' &nbsp; ',
-                             'escape_function' => \&encode_entities,
-                             'countrydefault'  => $countrydefault,
-                           )
-    %>
+      <% $loc->location_label( 'join_string'     => '<BR>',
+                               'double_space'    => ' &nbsp; ',
+                               'escape_function' => \&encode_entities,
+                               'countrydefault'  => $countrydefault,
+                             )
+      %>
 
-%   if ( $loc->latitude && $loc->longitude ) {
-        <BR>
-        <FONT SIZE=-1>
-        <% $loc->latitude %>, <% $loc->longitude %>
-        <& /elements/coord-links.html,
-             $loc->latitude,
-             $loc->longitude,
-             $opt{'cust_main'}->name_short. ': '. $opt{'part_pkg'}->pkg,
-             $opt{'cust_main'}->agentnum,
-        &>
-        </FONT>
-%   }
+%     if ( $loc->latitude && $loc->longitude ) {
+          <BR>
+          <FONT SIZE=-1>
+          <% $loc->latitude %>, <% $loc->longitude %>
+          <& /elements/coord-links.html,
+               $loc->latitude,
+               $loc->longitude,
+               $opt{'cust_main'}->name_short. ': '. $opt{'part_pkg'}->pkg,
+               $opt{'cust_main'}->agentnum,
+          &>
+          </FONT>
+%     }
+%     if ( $loc->censustract ) {
+         <BR>
+         <FONT SIZE=-1>
+         <% $loc->censustract %> (<% $loc->censusyear %> census)
+         </FONT>
+%     } elsif ( $conf->exists('cust_main-require_censustract') ) {
+          <BR>
+          <FONT SIZE=-1 COLOR="#ee3300">
+          <% emt('Census tract unknown') %>
+          </FONT>
+%     }
 
-%   if ( $default ) {
-    </DIV>
-%   }
+%     if ( $default ) {
+      </DIV>
+%     }
+%   } # all of this is hidden if packages are grouped by location, because
+%     # it's in the top banner
 
 %   if ( ! $cust_pkg->get('cancel')
 %      && $FS::CurrentUser::CurrentUser->access_right('Change customer package')
       (&nbsp;<%pkg_change_location_link($cust_pkg)%>&nbsp;)
 %     }
 %     if ( $cust_pkg->locationnum && ! $opt{no_links} ) {
-        (&nbsp;<%edit_location_link($cust_pkg->locationnum)%>&nbsp;)
+        (&nbsp;<%pkg_edit_location_link($cust_pkg->locationnum)%>&nbsp;)
 %     }
   </FONT>
 %   } 
-% }
+% } # if the package is a scheduled future package change without location
+%   # change, then don't show any of this at all.  It's all implied by the
+%   # preceding package.
 <%init>
 
 my $conf = new FS::Conf;
@@ -75,7 +91,7 @@ sub pkg_change_location_link {
   );
 }
 
-sub edit_location_link {
+sub pkg_edit_location_link {
   my $locationnum = shift;
   include( '/elements/popup_link.html',
     'action'      => $p. "edit/cust_location.cgi?locationnum=$locationnum",