Merge branch 'FREESIDE_3_BRANCH' of git.freeside.biz:/home/git/freeside into FREESIDE...
authorIvan Kohler <ivan@freeside.biz>
Sat, 13 Jun 2015 22:18:37 +0000 (15:18 -0700)
committerIvan Kohler <ivan@freeside.biz>
Sat, 13 Jun 2015 22:18:37 +0000 (15:18 -0700)
75 files changed:
FS/FS/ClientAPI.pm
FS/FS/ClientAPI/MasonComponent.pm
FS/FS/ClientAPI/MyAccount.pm
FS/FS/ClientAPI/Signup.pm
FS/FS/Conf.pm
FS/FS/L10N/es_cu.pm [new file with mode: 0644]
FS/FS/L10N/es_es.pm [new file with mode: 0644]
FS/FS/L10N/es_mx.pm [new file with mode: 0644]
FS/FS/L10N/es_us.pm [new file with mode: 0644]
FS/FS/Locales.pm
FS/FS/Record.pm
FS/FS/Schema.pm
FS/FS/cdr/FS/FS/cdr/vvs.pm [new file with mode: 0644]
FS/FS/cdr/earthlink.pm
FS/FS/cdr/enswitch.pm
FS/FS/cdr/ispphone.pm
FS/FS/cdr/vss.pm
FS/FS/cust_bill_pay_pkg.pm
FS/FS/cust_bill_pkg.pm
FS/FS/cust_main/Billing_Realtime.pm
FS/FS/cust_pkg.pm
FS/FS/part_event/Action/cust_bill_spool_csv.pm
FS/FS/part_event/Condition/cust_bill_age_before_payby.pm
FS/FS/part_pkg_link.pm
FS/FS/part_svc.pm
FS/FS/part_svc_column.pm
FS/FS/pay_batch/RBC.pm
FS/FS/svc_Common.pm
FS/FS/svc_acct.pm
FS/FS/svc_alarm.pm
FS/FS/svc_broadband.pm
FS/FS/svc_dish.pm
FS/FS/svc_hardware.pm
Makefile
bin/xmlrpc-customer_login_pay [new file with mode: 0644]
debian/control
debian/freeside-webui.links [deleted file]
debian/rules
fs_selfservice/FS-SelfService/SelfService.pm
fs_selfservice/FS-SelfService/cgi/invoices.html
fs_selfservice/FS-SelfService/cgi/myaccount.html
fs_selfservice/FS-SelfService/cgi/myaccount_menu.html
fs_selfservice/FS-SelfService/cgi/provision_list.html
fs_selfservice/FS-SelfService/cgi/provision_svc_forward.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/selfservice.cgi
fs_selfservice/FS-SelfService/cgi/view_usage_details.html
httemplate/browse/part_svc.cgi
httemplate/edit/elements/edit.html
httemplate/edit/elements/part_svc_column.html
httemplate/edit/elements/svc_Common.html
httemplate/edit/elements/svc_forward.html [new file with mode: 0644]
httemplate/edit/part_svc.cgi
httemplate/edit/svc_acct.cgi
httemplate/edit/svc_acct/communigate.html
httemplate/edit/svc_broadband.cgi
httemplate/edit/svc_forward.cgi
httemplate/elements/make_appointment.html [new file with mode: 0644]
httemplate/elements/menu.html
httemplate/elements/popup_link-make_appointment.html [new file with mode: 0644]
httemplate/elements/schedule-appointment.html [new file with mode: 0644]
httemplate/elements/tr-input-date-field.html
httemplate/elements/tr-select-hardware_type.html
httemplate/elements/tr-select-router_block_ip.html
httemplate/misc/payment.cgi
httemplate/search/cdr.html
httemplate/search/cust_bill_pay_pkg.html [new file with mode: 0644]
httemplate/search/report_cdr.html
httemplate/search/report_cust_bill_pay_pkg.html [new file with mode: 0644]
httemplate/view/Status.html
httemplate/view/cust_main/appointments.html
httemplate/view/cust_main/packages/services.html
httemplate/view/cust_main/packages/status.html
ng_selfservice/index.php
ng_selfservice/prepaid.php [new file with mode: 0644]
rt/lib/RT/Generated.pm [deleted file]

index e4031b2..1fea28c 100644 (file)
@@ -6,7 +6,7 @@ use vars qw( @EXPORT_OK %handler $domain $DEBUG $me );
 
 @EXPORT_OK = qw( load_clientapi_modules );
 
-$DEBUG = 1;
+$DEBUG = 0;
 $me = '[FS::ClientAPI]';
 
 %handler = ();
index b6f8aa4..50597e2 100644 (file)
@@ -22,6 +22,7 @@ my %allowed_comps = map { $_=>1 } qw(
   /misc/counties.cgi
   /misc/svc_acct-domains.cgi
   /misc/part_svc-columns.cgi
+  /edit/elements/svc_forward.html
 );
 
 my %session_comps = map { $_=>1 } qw(
index d9378d8..1152301 100644 (file)
@@ -139,6 +139,7 @@ sub skin_info {
       'logo' => scalar($conf->config_binary('logo.png', $agentnum )),
       ( map { $_ => join("\n", $conf->config("selfservice-$_", $agentnum ) ) }
         qw( head body_header body_footer company_address ) ),
+      'money_char' => $conf->config("money_char") || '$',
       'menu' => join("\n", $conf->config("ng_selfservice-menu", $agentnum ) ) ||
                 'main.php Home
 
@@ -443,11 +444,13 @@ sub customer_info {
     if ( $session->{'pkgnum'} ) {
       #XXX open invoices in the pkg-balances case
     } else {
+      $return{'money_char'} = $conf->config("money_char") || '$';
       my @open = map {
                        {
-                         invnum => $_->invnum,
-                         date   => time2str("%b %o, %Y", $_->_date),
-                         owed   => $_->owed,
+                         invnum     => $_->invnum,
+                         date       => time2str("%b %o, %Y", $_->_date),
+                         owed       => $_->owed,
+                         charged    => $_->charged,
                        };
                      } $cust_main->open_cust_bill;
       $return{open_invoices} = \@open;
@@ -1554,25 +1557,31 @@ sub list_invoices {
   my @cust_bill = grep ! $_->hide, $cust_main->cust_bill;
 
   my $balance = 0;
+  my $invoices = [
+    map {
+      #not super efficient, we also run cust_bill_pay/cust_credited inside owed
+      my @payments_and_credits = sort {$b->_date <=> $a->_date} ($_->cust_bill_pay,$_->cust_credited);
+      my $owed = $_->owed;
+      $balance += $owed;
+      +{ 'invnum'       => $_->invnum,
+         '_date'        => $_->_date,
+         'date'         => time2str("%b %o, %Y", $_->_date),
+         'date_short'   => time2str("%m-%d-%Y",  $_->_date),
+         'previous'     => sprintf('%.2f', ($_->previous)[0]),
+         'charged'      => sprintf('%.2f', $_->charged),
+         'owed'         => sprintf('%.2f', $owed),
+         'balance'      => sprintf('%.2f', $balance),
+         'lastpay'      => @payments_and_credits 
+                           ? time2str("%b %o, %Y", $payments_and_credits[0]->_date)
+                           : '',
+      }
+    } @cust_bill
+  ];
 
   return  { 'error'       => '',
             'balance'     => $cust_main->balance,
-            'invoices'    => [
-              map {
-                    my $owed = $_->owed;
-                    $balance += $owed;
-                    +{ 'invnum'       => $_->invnum,
-                       '_date'        => $_->_date,
-                       'date'         => time2str("%b %o, %Y", $_->_date),
-                       'date_short'   => time2str("%m-%d-%Y",  $_->_date),
-                       'previous'     => sprintf('%.2f', ($_->previous)[0]),
-                       'charged'      => sprintf('%.2f', $_->charged),
-                       'owed'         => sprintf('%.2f', $owed),
-                       'balance'      => sprintf('%.2f', $balance),
-                     }
-                  }
-                  @cust_bill
-            ],
+            'money_char'  => $conf->config("money_char") || '$',
+            'invoices'    => $invoices,
             'legacy_invoices' => [
               map {
                     +{ 'legacyinvnum' => $_->legacyinvnum,
@@ -2751,6 +2760,15 @@ sub provision_external {
             );
 }
 
+sub provision_forward {
+  my $p = shift;
+  _provision( 'FS::svc_forward',
+              ['srcsvc','src','dstsvc','dst'],
+              [],
+              $p,
+            );
+}
+
 sub _provision {
   my( $class, $fields, $return_fields, $p ) = splice(@_, 0, 4);
   warn "_provision called for $class\n"
@@ -2876,6 +2894,10 @@ sub part_svc_info {
         }
   }
 
+  if ($ret->{'svcdb'} eq 'svc_forward') {
+    $ret->{'forward_emails'} = {$cust_pkg->forward_emails()};
+  }
+
   $ret;
 }
 
index 6db33ac..547f230 100644 (file)
@@ -667,7 +667,6 @@ sub new_customer {
   my $part_pkg =
     qsearchs( 'part_pkg', { 'pkgpart' => $pkgpart } )
       or return { 'error' => "WARNING: unknown pkgpart: $pkgpart" };
-  my $svcpart = $part_pkg->svcpart($svc_x);
 
   my $reg_code = '';
   if ( $packet->{'reg_code'} ) {
@@ -685,50 +684,55 @@ sub new_customer {
   #my $error = $cust_pkg->check;
   #return { 'error' => $error } if $error;
 
-  #should be all auto-magic and shit
   my @svc = ();
-  if ( $svc_x eq 'svc_acct' ) {
+  unless ( $svc_x eq 'none' ) {
 
-    my $svc = new FS::svc_acct {
-      'svcpart'   => $svcpart,
-      map { $_ => $packet->{$_} }
-        qw( username _password sec_phrase popnum domsvc ),
-    };
-
-    my @acct_snarf;
-    my $snarfnum = 1;
-    while (    exists($packet->{"snarf_machine$snarfnum"})
-            && length($packet->{"snarf_machine$snarfnum"}) ) {
-      my $acct_snarf = new FS::acct_snarf ( {
-        'machine'   => $packet->{"snarf_machine$snarfnum"},
-        'protocol'  => $packet->{"snarf_protocol$snarfnum"},
-        'username'  => $packet->{"snarf_username$snarfnum"},
-        '_password' => $packet->{"snarf_password$snarfnum"},
-      } );
-      $snarfnum++;
-      push @acct_snarf, $acct_snarf;
-    }
-    $svc->child_objects( \@acct_snarf );
-    push @svc, $svc;
+    my $svcpart = $part_pkg->svcpart($svc_x);
+    #should be all auto-magic and shit
+    if ( $svc_x eq 'svc_acct' ) {
 
-  } elsif ( $svc_x eq 'svc_phone' ) {
+      my $svc = new FS::svc_acct {
+        'svcpart'   => $svcpart,
+        map { $_ => $packet->{$_} }
+          qw( username _password sec_phrase popnum domsvc ),
+      };
 
-    push @svc, new FS::svc_phone ( {
-      'svcpart' => $svcpart,
-       map { $_ => $packet->{$_} }
-         qw( countrycode phonenum sip_password pin ),
-    } );
+      my @acct_snarf;
+      my $snarfnum = 1;
+      while (    exists($packet->{"snarf_machine$snarfnum"})
+              && length($packet->{"snarf_machine$snarfnum"}) ) {
+        my $acct_snarf = new FS::acct_snarf ( {
+          'machine'   => $packet->{"snarf_machine$snarfnum"},
+          'protocol'  => $packet->{"snarf_protocol$snarfnum"},
+          'username'  => $packet->{"snarf_username$snarfnum"},
+          '_password' => $packet->{"snarf_password$snarfnum"},
+        } );
+        $snarfnum++;
+        push @acct_snarf, $acct_snarf;
+      }
+      $svc->child_objects( \@acct_snarf );
+      push @svc, $svc;
 
-  } elsif ( $svc_x eq 'svc_pbx' ) {
+    } elsif ( $svc_x eq 'svc_phone' ) {
 
-    push @svc, new FS::svc_pbx ( {
+      push @svc, new FS::svc_phone ( {
         'svcpart' => $svcpart,
-        map { $_ => $packet->{$_} } 
-          qw( id title ),
-        } );
+         map { $_ => $packet->{$_} }
+           qw( countrycode phonenum sip_password pin ),
+      } );
+
+    } elsif ( $svc_x eq 'svc_pbx' ) {
+
+      push @svc, new FS::svc_pbx ( {
+          'svcpart' => $svcpart,
+          map { $_ => $packet->{$_} } 
+            qw( id title ),
+          } );
   
-  } else {
-    die "unknown signup service $svc_x";
+    } else {
+      die "unknown signup service $svc_x";
+    }
+
   }
 
   if ($packet->{'mac_addr'} && $conf->exists('signup_server-mac_addr_svcparts'))
@@ -983,7 +987,6 @@ sub new_customer_minimal {
     my $part_pkg =
       qsearchs( 'part_pkg', { 'pkgpart' => $pkgpart } )
         or return { 'error' => "WARNING: unknown pkgpart: $pkgpart" };
-    my $svcpart = $part_pkg->svcpart($svc_x);
 
     my $cust_pkg = new FS::cust_pkg ( {
       #later#'custnum' => $custnum,
@@ -992,35 +995,40 @@ sub new_customer_minimal {
     #my $error = $cust_pkg->check;
     #return { 'error' => $error } if $error;
 
-    #should be all auto-magic and shit
-    if ( $svc_x eq 'svc_acct' ) {
+    unless ( $svc_x eq 'none' ) {
 
-      my $svc = new FS::svc_acct {
-        'svcpart'   => $svcpart,
-        map { $_ => $packet->{$_} }
-          qw( username _password sec_phrase popnum domsvc ),
-      };
+      my $svcpart = $part_pkg->svcpart($svc_x);
+      #should be all auto-magic and shit
+      if ( $svc_x eq 'svc_acct' ) {
 
-      push @svc, $svc;
+        my $svc = new FS::svc_acct {
+          'svcpart'   => $svcpart,
+          map { $_ => $packet->{$_} }
+            qw( username _password sec_phrase popnum domsvc ),
+        };
 
-    } elsif ( $svc_x eq 'svc_phone' ) {
+        push @svc, $svc;
 
-      push @svc, new FS::svc_phone ( {
-        'svcpart' => $svcpart,
-         map { $_ => $packet->{$_} }
-           qw( countrycode phonenum sip_password pin ),
-      } );
-
-    } elsif ( $svc_x eq 'svc_pbx' ) {
+      } elsif ( $svc_x eq 'svc_phone' ) {
 
-      push @svc, new FS::svc_pbx ( {
+        push @svc, new FS::svc_phone ( {
           'svcpart' => $svcpart,
-          map { $_ => $packet->{$_} } 
-            qw( id title ),
-          } );
+           map { $_ => $packet->{$_} }
+             qw( countrycode phonenum sip_password pin ),
+        } );
+
+      } elsif ( $svc_x eq 'svc_pbx' ) {
+
+        push @svc, new FS::svc_pbx ( {
+            'svcpart' => $svcpart,
+            map { $_ => $packet->{$_} } 
+              qw( id title ),
+            } );
     
-    } else {
-      die "unknown signup service $svc_x";
+      } else {
+        die "unknown signup service $svc_x";
+      }
+
     }
 
     foreach my $svc ( @svc ) {
@@ -1068,6 +1076,7 @@ sub new_customer_minimal {
                  'signup_service' => $svc_x,
                  'custnum'        => $cust_main->custnum,
                  'session_id'     => $session_id,
+                 map { $_ => $cust_main->$_ } qw( first last company ),
                );
 
   if ( $svc[0] ) {
index 38ccb6e..d211361 100644 (file)
@@ -2377,6 +2377,7 @@ and customer address. Include units.',
                        'svc_acct'  => 'Account (svc_acct)',
                        'svc_phone' => 'Phone number (svc_phone)',
                        'svc_pbx'   => 'PBX (svc_pbx)',
+                       'none'      => 'None - package only',
                      ],
   },
   
@@ -5964,6 +5965,13 @@ and customer address. Include units.',
     'type'        => 'checkbox',
   },
 
+  {
+    'key'         => 'default_appointment_length',
+    'section'     => 'UI',
+    'description' => 'Default appointment length, in minutes (30 minute granularity).',
+    'type'        => 'text',
+  },
+
   { 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" },
diff --git a/FS/FS/L10N/es_cu.pm b/FS/FS/L10N/es_cu.pm
new file mode 100644 (file)
index 0000000..f556c1f
--- /dev/null
@@ -0,0 +1,4 @@
+package FS::L10N::es_cu;
+use base qw(FS::L10N::es_es);
+
+1;
diff --git a/FS/FS/L10N/es_es.pm b/FS/FS/L10N/es_es.pm
new file mode 100644 (file)
index 0000000..4071731
--- /dev/null
@@ -0,0 +1,6 @@
+package FS::L10N::es_es;
+use base qw(FS::L10N::DBI);
+
+our %Lexicon = ();
+
+1;
diff --git a/FS/FS/L10N/es_mx.pm b/FS/FS/L10N/es_mx.pm
new file mode 100644 (file)
index 0000000..1901b9d
--- /dev/null
@@ -0,0 +1,4 @@
+package FS::L10N::es_mx;
+use base qw(FS::L10N::es_es);
+
+1;
diff --git a/FS/FS/L10N/es_us.pm b/FS/FS/L10N/es_us.pm
new file mode 100644 (file)
index 0000000..838b047
--- /dev/null
@@ -0,0 +1,4 @@
+package FS::L10N::es_us;
+use base qw(FS::L10N::es_es);
+
+1;
index 032c3b5..849929c 100644 (file)
@@ -31,6 +31,10 @@ tie our %locales, 'Tie::IxHash',
   'en_US', { name => 'English',        country => 'United States', },
   'en_AU', { name => 'English',        country => 'Australia', },
   'en_CA', { name => 'English',        country => 'Canada', },
+  'es_ES', { name => 'Spanish',        country => 'Spain', },
+  'es_CU', { name => 'Spanish',        country => 'Cuba', },
+  'es_MX', { name => 'Spanish',        country => 'Mexico', },
+  'es_US', { name => 'Spanish',        country => 'United States', },
   'fr_CA', { name => 'French',         country => 'Canada', },
   'fr_FR', { name => 'French',         country => 'France', },
   'fr_HT', { name => 'French',         country => 'Haiti', },
index 88d452a..72745fe 100644 (file)
@@ -3,7 +3,7 @@ package FS::Record;
 use strict;
 use vars qw( $AUTOLOAD @ISA @EXPORT_OK $DEBUG
              %virtual_fields_cache
-             $conf $conf_encryption $money_char $lat_lower $lon_upper
+             $money_char $lat_lower $lon_upper
              $me
              $nowarn_identical $nowarn_classload
              $no_update_diff $no_history $qsearch_qualify_columns
@@ -61,14 +61,20 @@ my $rsa_loaded;
 my $rsa_encrypt;
 my $rsa_decrypt;
 
-$conf = '';
-$conf_encryption = '';
+our $conf = '';
+our $conf_encryption = '';
+our $conf_encryptionmodule = '';
+our $conf_encryptionpublickey = '';
+our $conf_encryptionprivatekey = '';
 FS::UID->install_callback( sub {
 
   eval "use FS::Conf;";
   die $@ if $@;
   $conf = FS::Conf->new; 
-  $conf_encryption = $conf->exists('encryption');
+  $conf_encryption           = $conf->exists('encryption');
+  $conf_encryptionmodule     = $conf->config('encryptionmodule');
+  $conf_encryptionpublickey  = join("\n",$conf->config('encryptionpublickey'));
+  $conf_encryptionprivatekey = join("\n",$conf->config('encryptionprivatekey'));
   $money_char = $conf->config('money_char') || '$';
   my $nw_coords = $conf->exists('geocode-require_nw_coordinates');
   $lat_lower = $nw_coords ? 1 : -90;
@@ -1158,7 +1164,7 @@ sub insert {
   # Encrypt before the database
   if (    defined(eval '@FS::'. $table . '::encrypted_fields')
        && scalar( eval '@FS::'. $table . '::encrypted_fields')
-       && $conf->exists('encryption')
+       && $conf_encryption
   ) {
     foreach my $field (eval '@FS::'. $table . '::encrypted_fields') {
       next if $field eq 'payinfo' 
@@ -1399,7 +1405,7 @@ sub replace {
   
   # Encrypt for replace
   my $saved = {};
-  if (    $conf->exists('encryption')
+  if (    $conf_encryption
        && defined(eval '@FS::'. $new->table . '::encrypted_fields')
        && scalar( eval '@FS::'. $new->table . '::encrypted_fields')
   ) {
@@ -2160,7 +2166,7 @@ sub _h_statement {
   ;
 
   # If we're encrypting then don't store the payinfo in the history
-  if ( $conf && $conf->exists('encryption') && $self->table ne 'banned_pay' ) {
+  if ( $conf_encryption && $self->table ne 'banned_pay' ) {
     @fields = grep { $_ ne 'payinfo' } @fields;
   }
 
@@ -3057,7 +3063,7 @@ sub encrypt {
   my ($self, $value) = @_;
   my $encrypted = $value;
 
-  if ($conf->exists('encryption')) {
+  if ($conf_encryption) {
     if ($self->is_encrypted($value)) {
       # Return the original value if it isn't plaintext.
       $encrypted = $value;
@@ -3105,7 +3111,7 @@ You should generally not have to worry about calling this, as the system handles
 sub decrypt {
   my ($self,$value) = @_;
   my $decrypted = $value; # Will return the original value if it isn't encrypted or can't be decrypted.
-  if ($conf->exists('encryption') && $self->is_encrypted($value)) {
+  if ($conf_encryption && $self->is_encrypted($value)) {
     $self->loadRSA;
     if (ref($rsa_decrypt) =~ /::RSA/) {
       my $encrypted = unpack ("u*", $value);
@@ -3121,8 +3127,8 @@ sub loadRSA {
     #Initialize the Module
     $rsa_module = 'Crypt::OpenSSL::RSA'; # The Default
 
-    if ($conf->exists('encryptionmodule') && $conf->config('encryptionmodule') ne '') {
-      $rsa_module = $conf->config('encryptionmodule');
+    if ($conf_encryptionmodule && $conf_encryptionmodule ne '') {
+      $rsa_module = $conf_encryptionmodule;
     }
 
     if (!$rsa_loaded) {
@@ -3130,15 +3136,13 @@ sub loadRSA {
        $rsa_loaded++;
     }
     # Initialize Encryption
-    if ($conf->exists('encryptionpublickey') && $conf->config('encryptionpublickey') ne '') {
-      my $public_key = join("\n",$conf->config('encryptionpublickey'));
-      $rsa_encrypt = $rsa_module->new_public_key($public_key);
+    if ($conf_encryptionpublickey && $conf_encryptionpublickey ne '') {
+      $rsa_encrypt = $rsa_module->new_public_key($conf_encryptionpublickey);
     }
     
     # Intitalize Decryption
-    if ($conf->exists('encryptionprivatekey') && $conf->config('encryptionprivatekey') ne '') {
-      my $private_key = join("\n",$conf->config('encryptionprivatekey'));
-      $rsa_decrypt = $rsa_module->new_private_key($private_key);
+    if ($conf_encryptionprivatekey && $conf_encryptionprivatekey ne '') {
+      $rsa_decrypt = $rsa_module->new_private_key($conf_encryptionprivatekey);
     }
 }
 
index 5dcf3c3..3a27b74 100644 (file)
@@ -2447,6 +2447,7 @@ sub tables_hashref {
         'columnlabel', 'varchar', 'NULL', $char_d, '', '',
         'columnvalue', 'varchar', 'NULL',     512, '', '', 
         'columnflag',  'char',    'NULL',       1, '', '', 
+        'required',    'char',    'NULL',       1, '', '', 
       ],
       'primary_key' => 'columnnum',
       'unique' => [ [ 'svcpart', 'columnname' ] ],
diff --git a/FS/FS/cdr/FS/FS/cdr/vvs.pm b/FS/FS/cdr/FS/FS/cdr/vvs.pm
new file mode 100644 (file)
index 0000000..63a647e
--- /dev/null
@@ -0,0 +1,33 @@
+package FS::cdr::vvs;
+
+use strict;
+use vars qw( @ISA %info $tmp_mon $tmp_mday $tmp_year );
+use Time::Local;
+use FS::cdr qw(_cdr_date_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+%info = (
+  'name'          => 'VVS',
+  'weight'        => 120,
+  'header'        => 1,
+  'import_fields' => [
+
+        skip(1),        # i_customer
+        'accountcode',  # account_id
+        'src',          # caller
+        'dst',          # called
+        skip(2),        # reason
+                       # call id
+        _cdr_date_parser_maker('startdate'),       # time
+        'billsec',      # duration
+        skip(3),        # ringtime
+                        # status
+                        # resller_charge
+       'upstream_price',# customer_charge
+  ],
+);
+
+sub skip { map {''} (1..$_[0]) }
+
+1;
index 60cba65..213a025 100644 (file)
@@ -36,6 +36,8 @@ use Date::Parse;
         },                             #TERM NUMBER
        skip(2),                        #TERM CITY / TERM STATE
        _cdr_min_parser_maker,          #MINUTES
+       skip(1),                        #AMOUNT
+       'disposition',                  #Call Type
   ],
 );
 
index 1cdd802..8026293 100644 (file)
@@ -34,7 +34,13 @@ use FS::cdr_type;
                     #Inbound called type,
                    #Inbound called number,
                     #Inbound destination type, Inbound destination number,
-    'disposition',  #Outbound calling type,
+    sub { my ($cdr, $data) = @_;
+       $data ||= 'none';
+
+       my $cdr_type = qsearchs('cdr_type', { 'cdrtypename' => $data } );
+       $cdr->set('cdrtypenum', $cdr_type->cdrtypenum) if $cdr_type; 
+                } , #Outbound calling type,
+
       skip(11),     #Outbound calling number,
                     #Outbound called type, Outbound called number,
                     #Outbound destination type, Outbound destination number,
@@ -53,23 +59,4 @@ use FS::cdr_type;
 
 sub skip { map {''} (1..$_[0]) }
 
-#create CDR types with names matching in_calling_type valuesj - 'none'
-# (without the quotes) for blank
-our %cdr_type = ();
-sub in_calling_type {
-  my ($record, $data) = @_;
-
-  $data ||= 'none';
-
-  my $cdr_type = exists($cdr_type{$data})
-                   ? $cdr_type{$data}
-                   : qsearchs('cdr_type', { 'cdrtypename' => $data } );
-
-  $cdr_type{$data} = $cdr_type;
-
-  $record->set('in_calling_type', $data); #for below
-  $record->set('cdrtypenum', $cdr_type->cdrtypenum) if $cdr_type;
-
-}
-
 1;
index 2817d53..c2ba186 100644 (file)
@@ -15,8 +15,16 @@ use Date::Parse;
   'import_fields' => [
 
                  'accountcode',  # Accountcode
-                        'src',  # Form
-                        'dst',  # To
+                       sub { my ($cdr, $src) = @_;
+                               $src =~ s/^\s+//;
+                                $cdr->set('src', $src);
+
+                        },       # Form
+                       sub { my ($cdr, $dst) = @_;
+                                $dst =~ s/^\s+//;
+                                $cdr->set('dst', $dst);
+
+                        },       # To
                       skip(1),  # Country
      'upstream_dst_regionname',  # Description
 _cdr_date_parser_maker('startdate'),  #DateTime
index 6fc647a..a550303 100644 (file)
@@ -13,7 +13,6 @@ use FS::cdr qw(_cdr_date_parser_maker);
   'header'        => 1,
   'import_fields' => [
 
-        skip(1),        # Customer
         skip(1),        # i_customer
         'accountcode',  # account_id
         'src',          # caller
index 5d154d0..24ca797 100644 (file)
@@ -1,14 +1,12 @@
 package FS::cust_bill_pay_pkg;
+use base qw( FS::cust_main_Mixin FS::Record );
 
 use strict;
-use vars qw( @ISA );
 use FS::Conf;
 use FS::Record qw( qsearch qsearchs );
 use FS::cust_bill_pay;
 use FS::cust_bill_pkg;
 
-@ISA = qw(FS::Record);
-
 =head1 NAME
 
 FS::cust_bill_pay_pkg - Object methods for cust_bill_pay_pkg records
index 70c7adc..4448da6 100644 (file)
@@ -837,6 +837,8 @@ sub _item_discount {
     description     => $self->mt('Discount'),
     amount          => 0,
     ext_description => \@ext,
+    pkgpart         => $self->pkgpart,
+    feepart         => $self->feepart,
     # maybe should show quantity/unit discount?
   };
   foreach my $pkg_discount (@pkg_discounts) {
index d4bd1f3..5980bf5 100644 (file)
@@ -343,8 +343,9 @@ sub realtime_bop {
   my $cc_surcharge = 0;
   my $cc_surcharge_pct = 0;
   $cc_surcharge_pct = $conf->config('credit-card-surcharge-percentage') 
-    if $conf->config('credit-card-surcharge-percentage');
-  
+    if $conf->config('credit-card-surcharge-percentage')
+    && $options{method} eq 'CC';
+
   # always add cc surcharge if called from event 
   if($options{'cc_surcharge_from_event'} && $cc_surcharge_pct > 0) {
       $cc_surcharge = $options{'amount'} * $cc_surcharge_pct / 100;
index a03a446..14555dd 100644 (file)
@@ -2043,7 +2043,7 @@ sub change {
       return "modifying package: $error";
     } else {
       $dbh->commit if $oldAutoCommit;
-      return '';
+      return $self;
     }
   }
 
@@ -3723,6 +3723,7 @@ Returns the parent customer object (see L<FS::cust_main>).
 
 sub cust_main {
   my $self = shift;
+  cluck 'cust_pkg->cust_main called' if $DEBUG;
   qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
 }
 
@@ -5666,6 +5667,78 @@ sub bulk_change {
   '';
 }
 
+=item forward_emails
+
+Returns a hash of svcnums and corresponding email addresses
+for svc_acct services that can be used as source or dest
+for svc_forward services provisioned in this package.
+
+Accepts options I<svc_forward> OR I<svcnum> for a svc_forward
+service;  if included, will ensure the current values of the
+specified service are included in the list, even if for some
+other reason they wouldn't be.  If called as a class method
+with a specified service, returns only these current values.
+
+Caution: does not actually check if svc_forward services are
+available to be provisioned on this package.
+
+=cut
+
+sub forward_emails {
+  my $self = shift;
+  my %opt = @_;
+
+  #load optional service, thoroughly validated
+  die "Use svcnum or svc_forward, not both"
+    if $opt{'svcnum'} && $opt{'svc_forward'};
+  my $svc_forward = $opt{'svc_forward'};
+  $svc_forward ||= qsearchs('svc_forward',{ 'svcnum' => $opt{'svcnum'} })
+    if $opt{'svcnum'};
+  die "Specified service is not a forward service"
+    if $svc_forward && (ref($svc_forward) ne 'FS::svc_forward');
+  die "Specified service not found"
+    if ($opt{'svcnum'} || $opt{'svc_forward'}) && !$svc_forward;
+
+  my %email;
+
+  ## everything below was basically copied from httemplate/edit/svc_forward.cgi 
+  ## with minimal refactoring, not sure why we can't just load all svc_accts for this custnum
+
+  #add current values from specified service, if there was one
+  if ($svc_forward) {
+    foreach my $method (qw( srcsvc_acct dstsvc_acct )) {
+      my $svc_acct = $svc_forward->$method();
+      $email{$svc_acct->svcnum} = $svc_acct->email if $svc_acct;
+    }
+  }
+
+  if (ref($self) eq 'FS::cust_pkg') {
+
+    #and including the rest for this customer
+    my($u_part_svc,@u_acct_svcparts);
+    foreach $u_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_acct'}) ) {
+      push @u_acct_svcparts,$u_part_svc->getfield('svcpart');
+    }
+
+    my $custnum = $self->getfield('custnum');
+    foreach my $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) {
+      my $cust_pkgnum = $i_cust_pkg->getfield('pkgnum');
+      #now find the corresponding record(s) in cust_svc (for this pkgnum!)
+      foreach my $acct_svcpart (@u_acct_svcparts) {
+        foreach my $i_cust_svc (
+          qsearch( 'cust_svc', { 'pkgnum'  => $cust_pkgnum,
+                                 'svcpart' => $acct_svcpart } )
+        ) {
+          my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $i_cust_svc->svcnum } );
+          $email{$svc_acct->svcnum} = $svc_acct->email;
+        }  
+      }
+    }
+  }
+
+  return %email;
+}
+
 # Used by FS::Upgrade to migrate to a new database.
 sub _upgrade_data {  # class method
   my ($class, %opts) = @_;
index 250c830..0d84e77 100644 (file)
@@ -33,6 +33,10 @@ sub option_fields {
                              empty_label => '(do not upload)',
                              order_by => 'targetnum',
                            },
+    'skip_nopost' => { label => 'Skip customers without postal billing enabled',
+                       type  => 'checkbox',
+                       value => 'Y',
+                     },
   );
 }
 
@@ -44,6 +48,9 @@ sub do_action {
   #my $cust_main = $self->cust_main($cust_bill);
   my $cust_main = $cust_bill->cust_main;
 
+  return if $self->option('skip_nopost')
+         && ! grep { $_ eq 'POST' } $cust_main->invoicing_list;
+
   $cust_bill->spool_csv(
     'time'         => $cust_event->_date,
     'format'       => $self->option('spoolformat'),
index 96d9da8..5a81fbb 100644 (file)
@@ -34,12 +34,12 @@ sub condition {
             'order_by' => 'ORDER BY history_date DESC LIMIT 1',
         }))
   {
-    my $newest = $replace_new->history_date;
+    $newest = $replace_new->history_date;
     my $replace_old = qsearchs({
       'table' => 'h_cust_main',
       'hashref' => { 'custnum'        => $replace_new->custnum,
                      'history_action' => 'replace_old',
-                     'history_date'   => $replace_new->history_date,
+                     'history_date'   => $replace_new->history_date, #fuzz?
                    }
     }) or next; #no replace_old?  ignore and continue on i guess
 
index 8e43d15..ce071ef 100644 (file)
@@ -130,6 +130,7 @@ sub insert {
     return $error if $error;
   }
 
+  $dbh->commit if $oldAutoCommit;
   return;
 }
 
@@ -166,7 +167,7 @@ sub delete {
     $dbh->rollback if $oldAutoCommit;
     return $error;
   }
-  $dbh->commit;
+  $dbh->commit if $oldAutoCommit;
   return;
 }
 
index c2003c4..8a5fc35 100644 (file)
@@ -96,8 +96,12 @@ the part_svc_column table appropriately (see L<FS::part_svc_column>).
 
 =item I<svcdb>__I<field> - Default or fixed value for I<field> in I<svcdb>.
 
+=item I<svcdb>__I<field>_label
+
 =item I<svcdb>__I<field>_flag - defines I<svcdb>__I<field> action: null or empty (no default), `D' for default, `F' for fixed (unchangeable), , `S' for selectable choice, `M' for manual selection from inventory, or `A' for automatic selection from inventory.  For virtual fields, can also be 'X' for excluded.
 
+=item I<svcdb>__I<field>_required - I<field> should always have a true value
+
 =back
 
 If you want to add part_svc_column records for fields that do not exist as
@@ -139,52 +143,53 @@ sub insert {
   # add part_svc_column records
 
   my $svcdb = $self->svcdb;
-#  my @rows = map { /^${svcdb}__(.*)$/; $1 }
-#    grep ! /_flag$/,
-#      grep /^${svcdb}__/,
-#        fields('part_svc');
-  foreach my $field (
-    grep { $_ ne 'svcnum'
-           && ( defined( $self->getfield($svcdb.'__'.$_.'_flag') )
-                || $self->getfield($svcdb.'__'.$_.'_label') !~ /^\s*$/ )
-         } (fields($svcdb), @fields)
-  ) {
-    my $part_svc_column = $self->part_svc_column($field);
-    my $previous = qsearchs('part_svc_column', {
-      'svcpart'    => $self->svcpart,
-      'columnname' => $field,
-    } );
+  foreach my $field (fields($svcdb), @fields) {
+    next if $field eq 'svcnum';
+    my $prefix = $svcdb.'__';
+    if ( defined( $self->getfield($prefix.$field.'_flag'))
+      or defined($self->getfield($prefix.$field.'_required'))
+      or length($self->getfield($prefix.$field.'_label'))
+    ) {
+      my $part_svc_column = $self->part_svc_column($field);
+      my $previous = qsearchs('part_svc_column', {
+        'svcpart'    => $self->svcpart,
+        'columnname' => $field,
+      } );
 
-    my $flag  = $self->getfield($svcdb.'__'.$field.'_flag');
-    my $label = $self->getfield($svcdb.'__'.$field.'_label');
-    if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) {
+      my $flag  = $self->getfield($prefix.$field.'_flag');
+      my $label = $self->getfield($prefix.$field.'_label');
+      my $required = $self->getfield($prefix.$field.'_required') ? 'Y' : '';
+      if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) {
 
-      if ( uc($flag) =~ /^([A-Z])$/ ) {
-        my $parser = FS::part_svc->svc_table_fields($svcdb)->{$field}->{parse}
-                     || sub { shift };
-        $part_svc_column->setfield('columnflag', $1);
-        $part_svc_column->setfield('columnvalue',
-          &$parser($self->getfield($svcdb.'__'.$field))
-        );
-      }
+        if ( uc($flag) =~ /^([A-Z])$/ ) {
+          my $parser = FS::part_svc->svc_table_fields($svcdb)->{$field}->{parse}
+                       || sub { shift };
+          $part_svc_column->setfield('columnflag', $1);
+          $part_svc_column->setfield('columnvalue',
+            &$parser($self->getfield($prefix.$field))
+          );
+        }
 
-      $part_svc_column->setfield('columnlabel', $label)
-        if $label !~ /^\s*$/;
+        $part_svc_column->setfield('columnlabel', $label)
+          if $label !~ /^\s*$/;
+
+        $part_svc_column->setfield('required', $required);
+
+        if ( $previous ) {
+          $error = $part_svc_column->replace($previous);
+        } else {
+          $error = $part_svc_column->insert;
+        }
 
-      if ( $previous ) {
-        $error = $part_svc_column->replace($previous);
       } else {
-        $error = $part_svc_column->insert;
+        $error = $previous ? $previous->delete : '';
+      }
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return $error;
       }
 
-    } else {
-      $error = $previous ? $previous->delete : '';
-    }
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return $error;
     }
-
   }
 
   # add export_svc records
@@ -277,50 +282,54 @@ sub replace {
    # maintain part_svc_column records
 
     my $svcdb = $new->svcdb;
-    foreach my $field (
-      grep { $_ ne 'svcnum'
-             && ( defined( $new->getfield($svcdb.'__'.$_.'_flag') )
-                  || $new->getfield($svcdb.'__'.$_.'_label') !~ /^\s*$/ )
-           } (fields($svcdb),@fields)
-    ) {
-
-      my $part_svc_column = $new->part_svc_column($field);
-      my $previous = qsearchs('part_svc_column', {
-        'svcpart'    => $new->svcpart,
-        'columnname' => $field,
-      } );
-
-      my $flag  = $new->getfield($svcdb.'__'.$field.'_flag');
-      my $label = $new->getfield($svcdb.'__'.$field.'_label');
+    foreach my $field (fields($svcdb),@fields) {
+      next if $field eq 'svcnum';
+      my $prefix = $svcdb.'__';
+      if ( defined( $new->getfield($prefix.$field.'_flag'))
+        or defined($new->getfield($prefix.$field.'_required'))
+        or length($new->getfield($prefix.$field.'_label'))
+      ) {
+        my $part_svc_column = $new->part_svc_column($field);
+        my $previous = qsearchs('part_svc_column', {
+          'svcpart'    => $new->svcpart,
+          'columnname' => $field,
+        } );
+
+        my $flag  = $new->getfield($svcdb.'__'.$field.'_flag');
+        my $label = $new->getfield($svcdb.'__'.$field.'_label');
+        my $required = $new->getfield($svcdb.'__'.$field.'_required') ? 'Y' : '';
  
-      if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) {
+        if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) {
+
+          if ( uc($flag) =~ /^([A-Z])$/ ) {
+            $part_svc_column->setfield('columnflag', $1);
+            my $parser = FS::part_svc->svc_table_fields($svcdb)->{$field}->{parse}
+                       || sub { shift };
+            $part_svc_column->setfield('columnvalue',
+              &$parser($new->getfield($svcdb.'__'.$field))
+            );
+          } else {
+            $part_svc_column->setfield('columnflag',  '');
+            $part_svc_column->setfield('columnvalue', '');
+          }
 
-        if ( uc($flag) =~ /^([A-Z])$/ ) {
-          $part_svc_column->setfield('columnflag', $1);
-          my $parser = FS::part_svc->svc_table_fields($svcdb)->{$field}->{parse}
-                     || sub { shift };
-          $part_svc_column->setfield('columnvalue',
-            &$parser($new->getfield($svcdb.'__'.$field))
-          );
-        } else {
-          $part_svc_column->setfield('columnflag',  '');
-          $part_svc_column->setfield('columnvalue', '');
-        }
+          $part_svc_column->setfield('columnlabel', $label)
+            if $label !~ /^\s*$/;
 
-        $part_svc_column->setfield('columnlabel', $label)
-          if $label !~ /^\s*$/;
+          $part_svc_column->setfield('required', $required);
 
-        if ( $previous ) {
-          $error = $part_svc_column->replace($previous);
+          if ( $previous ) {
+            $error = $part_svc_column->replace($previous);
+          } else {
+            $error = $part_svc_column->insert;
+          }
         } else {
-          $error = $part_svc_column->insert;
+          $error = $previous ? $previous->delete : '';
+        }
+        if ( $error ) {
+          $dbh->rollback if $oldAutoCommit;
+          return $error;
         }
-      } else {
-        $error = $previous ? $previous->delete : '';
-      }
-      if ( $error ) {
-        $dbh->rollback if $oldAutoCommit;
-        return $error;
       }
     }
 
@@ -594,6 +603,7 @@ sub svc_x {
 =cut
 
 my $svc_defs;
+my $svc_info;
 sub _svc_defs {
 
   return $svc_defs if $svc_defs; #cache
@@ -648,7 +658,14 @@ sub _svc_defs {
     sort { $info{$a}->{'display_weight'} <=> $info{$b}->{'display_weight'} }
     keys %info,
   ;
-  
+
+  tie my %svc_info, 'Tie::IxHash',
+    map  { $_ => $info{$_} }
+    sort { $info{$a}->{'display_weight'} <=> $info{$b}->{'display_weight'} }
+    keys %info,
+  ;
+    
+  $svc_info = \%svc_info; #access via svc_table_info  
   $svc_defs = \%svc_defs; #cache
   
 }
@@ -700,6 +717,8 @@ some components specified by "select-.*.html", and a bunch more...
 
 =item select_allow_empty - Used with select_table, adds an empty option
 
+=item required - This field should always have a true value (do not use with type checkbox or disabled)
+
 =back
 
 =cut
@@ -722,6 +741,27 @@ sub svc_table_fields {
   $def;
 }
 
+=item svc_table_info TABLE
+
+Returns table_info for TABLE from cache, or empty
+hashref if none is found.
+
+Caution:  caches table_info for ALL services when run;
+access a service's table_info directly unless you know
+you're loading them all.
+
+Caution:  does not standardize fields into hashrefs;
+use L</svc_table_fields> to access fields.
+
+=cut
+
+sub svc_table_info {
+  my $class = shift;
+  my $table = shift;
+  $class->_svc_defs; #creates cache if needed
+  return $svc_info->{$table} || {};
+}
+
 =back
 
 =head1 SUBROUTINES
@@ -777,7 +817,7 @@ sub process {
                          and ref($param->{ $f }) ) {
                       $param->{ $f } = join(',', @{ $param->{ $f } });
                    }
-                    ( $f, $f.'_flag', $f.'_label' );
+                    ( $f, $f.'_flag', $f.'_label', $f.'_required' );
                   }
                   @fields;
 
index 38ce1fa..75a2dfb 100644 (file)
@@ -45,6 +45,8 @@ fields are currently supported:
 
 =item columnflag - null or empty (no default), `D' for default, `F' for fixed (unchangeable), `S' for selectable choice, `M' for manual selection from inventory, `A' for automatic selection from inventory, or `H' for selection from a hardware class.  For virtual fields, can also be 'X' for excluded.
 
+=item required - column value expected to be true
+
 =back
 
 =head1 METHODS
@@ -91,6 +93,7 @@ sub check {
     || $self->ut_alpha('columnname')
     || $self->ut_textn('columnlabel')
     || $self->ut_anything('columnvalue')
+    || $self->ut_flag('required')
   ;
   return $error if $error;
 
index 45e888d..47fc8d4 100644 (file)
@@ -4,6 +4,7 @@ use strict;
 use vars qw(@ISA %import_info %export_info $name);
 use Date::Format 'time2str';
 use FS::Conf;
+use Encode 'encode';
 
 my $conf;
 my ($client_num, $shortname, $longname, $trans_code, $testmode, $i, $declined, $totaloffset);
@@ -173,9 +174,9 @@ $name = 'RBC';
     ' '.
     sprintf("%010.0f",$cust_pay_batch->amount*100).
     '      '.
-    time2str("%Y%j", $pay_batch->download).
-    sprintf("%-30s", $cust_pay_batch->cust_main->first . ' ' .
-                     $cust_pay_batch->cust_main->last).
+    time2str("%Y%j", time + 86400).
+    sprintf("%-30.30s", encode('utf8', $cust_pay_batch->cust_main->first . ' ' .
+                     $cust_pay_batch->cust_main->last)).
     'E'. # English
     ' '.
     sprintf("%-15s", $shortname).
index 61a5f6a..d70209a 100644 (file)
@@ -155,13 +155,48 @@ sub cust_linked {
 
 Checks the validity of fields in this record.
 
-At present, this does nothing but call FS::Record::check (which, in turn, 
-does nothing but run virtual field checks).
+Only checks fields marked as required in table_info or 
+part_svc_column definition.  Should be invoked by service-specific
+check using SUPER.  Invokes FS::Record::check using SUPER.
 
 =cut
 
 sub check {
   my $self = shift;
+
+  ## Checking required fields
+
+  # get fields marked as required in table_info
+  my $required = {};
+  my $labels = {};
+  my $tinfo = $self->can('table_info') ? $self->table_info : {};
+  if ($tinfo->{'manual_require'}) {
+    my $fields = $tinfo->{'fields'} || {};
+    foreach my $field (keys %$fields) {
+      if (ref($fields->{$field}) && $fields->{$field}->{'required'}) {
+        $required->{$field} = 1;
+        $labels->{$field} = $fields->{$field}->{'label'};
+      }
+    }
+    # add fields marked as required in database
+    foreach my $column (
+      qsearch('part_svc_column',{
+        'svcpart' => $self->svcpart,
+        'required' => 'Y'
+      })
+    ) {
+      $required->{$column->columnname} = 1;
+      $labels->{$column->columnname} = $column->columnlabel;
+    }
+    # do the actual checking
+    foreach my $field (keys %$required) {
+      unless (length($self->get($field)) > 0) {
+        my $name = $labels->{$field} || $field;
+        return "$name is required\n"
+      }
+    }
+  }
+
   $self->SUPER::check;
 }
 
index 93e3f2c..9636b3e 100644 (file)
@@ -261,6 +261,7 @@ sub table_info {
     'display_weight' => 10,
     'cancel_weight'  => 50, 
     'ip_field' => 'slipip',
+    'manual_require' => 1,
     'fields' => {
         'dir'       => 'Home directory',
         'uid'       => {
@@ -284,6 +285,7 @@ sub table_info {
                          disable_default => 1,
                          disable_fixed => 1,
                          disable_select => 1,
+                         required => 1,
                        },
         'password_selfchange' => { label => 'Password modification',
                                    type  => 'checkbox',
@@ -311,7 +313,9 @@ sub table_info {
                          type => 'text',
                          disable_inventory => 1,
                        },
-        '_password' => 'Password',
+        '_password' => { label => 'Password',
+                         required => 1
+                       },
         'gid'       => {
                          label    => 'GID',
                         def_info => 'when blank, defaults to UID',
@@ -334,6 +338,7 @@ sub table_info {
                          select_key   => 'svcnum',
                          select_label => 'domain',
                          disable_inventory => 1,
+                         required => 1,
                        },
         'pbxsvc'    => { label => 'PBX',
                          type  => 'select-svc_pbx.html',
index 0624f18..2332f11 100644 (file)
@@ -214,6 +214,10 @@ sub check {
   ;
   return $error if $error;
 
+  #really just an signed int, but to discourage storing other data (e.g. phone)
+  return 'CS Receiver must be 9 digits or less'
+    if $self->cs_receiver =~ /\d{10}/;
+
   $self->SUPER::check;
 }
 
index 40f2c9b..2cdc284 100755 (executable)
@@ -103,6 +103,7 @@ sub table_info {
     'display_weight' => 50,
     'cancel_weight'  => 70,
     'ip_field' => 'ip_addr',
+    'manual_require' => 1,
     'fields' => {
       'svcnum'      => 'Service',
       'description' => 'Descriptive label',
index 5c9e217..2d249d1 100644 (file)
@@ -63,9 +63,10 @@ sub table_info {
     'name'           => 'Dish service',
     'display_weight' => 58,
     'cancel_weight'  => 85,
+    'manual_require' => 1,
     'fields' => {
       'svcnum'    =>  { label => 'Service' },
-      'acctnum'   =>  { label => 'DISH account#', %opts },
+      'acctnum'   =>  { label => 'DISH account#', required => 1, %opts },
       'installdate' => { label => 'Install date', %opts },
       'note'      => { label => 'Installation notes', %opts },
     }
index d9c3c46..2be8954 100644 (file)
@@ -76,6 +76,7 @@ sub table_info {
     'name_plural'    => 'Hardware',
     'display_weight' => 59,
     'cancel_weight'  => 86,
+    'manual_require' => 1,
     'fields' => {
       'svcnum'    => { label => 'Service' },
       'typenum'   => { label => 'Device type',
@@ -84,6 +85,7 @@ sub table_info {
                        disable_fixed     => 1,
                        disable_default   => 1,
                        disable_inventory => 1,
+                       required => 1,
                      },
       'serial'    => { label => 'Serial number', %opts },
       'hw_addr'   => { label => 'Hardware address', %opts },
index 5a1d61a..ab27c00 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -287,7 +287,7 @@ install-init:
 install-apache:
        [ -e ${APACHE_CONF}/freeside-base.conf ] && rm ${APACHE_CONF}/freeside-base.conf || true
        [ -d ${APACHE_CONF} ] && \
-         ( install -o root -m 755 htetc/freeside-base${APACHE_VERSION}.conf ${APACHE_CONF} && \
+         ( install -o root -m 755 htetc/freeside-base2.conf ${APACHE_CONF} && \
            ( [ ${RT_ENABLED} -eq 1 ] && install -o root -m 755 htetc/freeside-rt.conf ${APACHE_CONF} || true ) && \
            ( [ ${TORRUS_ENABLED} -eq 1 ] && install -o root -m 755 htetc/freeside-torrus.conf ${APACHE_CONF} || true ) && \
            perl -p -i -e "\
diff --git a/bin/xmlrpc-customer_login_pay b/bin/xmlrpc-customer_login_pay
new file mode 100644 (file)
index 0000000..a867a69
--- /dev/null
@@ -0,0 +1,50 @@
+#!/usr/bin/perl
+
+use strict;
+use Frontier::Client;
+use Data::Dumper;
+
+my( $username, $password ) = ( @ARGV );
+
+my $uri = new URI 'http://localhost:8080/';
+
+my $server = new Frontier::Client ( 'url' => $uri );
+
+my $result = $server->call('FS.ClientAPI_XMLRPC.login',
+
+       'email'    => 'joe@test.com',
+        'password' => 'testpass',
+);
+
+die $result->{'error'} if $result->{'error'};
+
+my $session_id = $result->{'session_id'};
+warn "$session_id";
+
+my $process_payment = $server->call('FS.ClientAPI_XMLRPC.process_payment',
+
+        # Required fields
+       'session_id'  => $session_id,
+       'amount'      => '1.00',
+        'payname'     => 'Joe Tester',
+       'payinfo'     => '4111111111111111',
+       'month'       => '1', # CC Expiration month
+       'year'        => '2019', # CC Expiration year
+
+       #Optional fields (Only needed which these are not set on the customers account or when new information needs to be saved)
+       'save'        => 1, # Save this information to the customers account
+       'auto'        => 1, # Set this customers account to "automatic" payment type
+       'address1'    => '1234 Testerville Rd',
+       'address2'    => '',
+       'city'        => 'Testerville',
+       'state'       => 'MD',
+       'zip'         => '12345',
+       'country'     => 'US',
+       
+
+
+);     
+
+die $process_payment->{'error'} if $process_payment->{'error'};
+
+1;
index 06c5bbc..986178a 100644 (file)
@@ -21,7 +21,7 @@ Description: Billing and trouble ticketing for service providers
 
 Package: freeside-lib
 Architecture: all
-Depends: gnupg,ghostscript,gsfonts,gzip,latex-xcolor,libbusiness-creditcard-perl,libcache-cache-perl,libcache-simple-timedexpiry-perl,libchart-perl,libclass-container-perl,libclass-data-inheritable-perl,libclass-returnvalue-perl,libcolor-scheme-perl,libcompress-zlib-perl,libconvert-binhex-perl,libcrypt-passwdmd5-perl,libcrypt-ssleay-perl,libcss-squish-perl,libdate-manip-perl,libdbd-mysql-perl,libdbd-pg-perl,libdbi-perl,libdbix-dbschema-perl,libdbix-searchbuilder-perl,libdevel-stacktrace-perl,libdevel-symdump-perl,liberror-perl,libexcel-writer-xlsx-perl,libexception-class-perl,libfile-counterfile-perl,libfile-rsync-perl,libfont-afm-perl,libfreezethaw-perl,libfrontier-rpc-perl,libgd-gd2-perl,libgd-graph-perl,libhtml-format-perl,libhtml-mason-perl,libhtml-parser-perl,libhtml-scrubber-perl,libhtml-tagset-perl,libhtml-tree-perl,libhtml-widgets-selectlayers-perl,libio-stringy-perl,libipc-run-perl,libipc-run3-perl,libipc-sharelite-perl,libjavascript-rpc-perl,libjson-perl,liblingua-en-inflect-perl,liblingua-en-nameparse-perl,liblocale-gettext-perl,liblocale-maketext-fuzzy-perl,liblocale-maketext-lexicon-perl,liblocale-subcountry-perl,liblog-dispatch-perl,libmailtools-perl,libmime-tools-perl,libmodule-versions-report-perl,libnet-daemon-perl,libnet-ping-external-perl,libnet-scp-perl,libnet-ssh-perl,libnet-whois-raw-perl,libnetaddr-ip-perl,libnumber-format-perl,libpam-modules,libpam-runtime,libpaper-utils,libparams-validate-perl,libparse-recdescent-perl,libpg-perl,libregexp-common-perl,libspreadsheet-writeexcel-perl,libstring-approx-perl,libstring-shellquote-perl,libterm-readkey-perl,libtest-inline-perl,libtext-autoformat-perl,libtext-charwidth-perl,libtext-csv-perl,libtext-iconv-perl,libtext-quoted-perl,libtext-reform-perl,libtext-template-perl,libtext-wrapi18n-perl,libtext-wrapper-perl,libtie-ixhash-perl,libtime-duration-perl,libtime-modules-perl,libtimedate-perl,libtree-simple-perl,libuniversal-require-perl,liburi-perl,libwant-perl,libwww-perl,libxml-parser-perl,libyaml-perl,lmodern,locales,lpr,mime-support,ntp,ntpdate,perl,perl-base,perl-modules,postgresql,postgresql-client,psutils,rsync,openssh-client,texlive,texlive-latex-extra,texinfo,ttf-bitstream-vera,ttf-dustin,libdatetime-perl,libdatetime-format-strptime-perl,libfile-slurp-perl,libspreadsheet-parseexcel-perl,libauthen-passphrase-perl,libnet-domain-tld-perl,libbusiness-us-usps-webtools-perl,libxml-simple-perl,libemail-sender-perl,libemail-sender-transport-smtp-tls-perl,libemail-sender-perl,libemail-sender-transport-smtp-tls-perl,libhtml-defang-perl,libdatetime-format-natural-perl,libcgi-pm-perl,libfile-sharedir-perl,libmodule-versions-report-perl,libtext-wikiformat-perl,libnet-server-perl,libhttp-server-simple-perl,libhtml-rewriteattributes-perl,libmime-types-perl,libperlio-eol-perl,libgnupg-interface-perl,libdata-ical-perl,libcalendar-simple-perl,libdatetime-set-perl,libhook-lexwrap-perl,libhttp-server-simple-mason-perl,libxml-rss-perl,libipc-run-safehandles-perl,libpoe-perl,libsoap-lite-perl,libhtml-tableextract-perl,libhtml-element-extended-perl,libcam-pdf-perl,libnet-openssh-perl,libgd-barcode-perl,sam2p,libsys-sigaction-perl,libgeo-googleearth-pluggable-perl,libgeo-coder-googlev3-perl,libnet-snmp-perl,libcrypt-openssl-rsa-perl,libregexp-common-perl,libnet-cidr-perl,libregexp-ipv6-perl,libhtml-quoted-perl,libtext-password-pronounceable-perl,libconvert-color-perl,liburi-perl,libhtml-rewriteattributes-perl,libregexp-common-net-cidr-perl,liblog-dispatch-perl,libdbix-searchbuilder-perl,libencode-perl,libhtml-mason-psgihandler-perl,libdate-simple-perl,libsnmp-perl,libemail-valid-perl,libio-string-perl,libnet-smtp-ssl-perl,libgeo-coder-googlev3-perl,libcam-pdf-perl,libnet-openssh-perl,libhtml-quoted-perl,libregexp-ipv6-perl,libregexp-common-net-cidr-perl,libencode-perl,libexcel-writer-xlsx-perl,libgeo-googleearth-pluggable-perl,libhtml-mason-psgihandler-perl,libdate-simple-perl,libsnmp-perl,libemail-valid-perl,libparse-fixedlength-perl,libregexp-common-net-cidr-perl,libio-string-perl,libnet-mac-vendor-perl
+Depends: gnupg,ghostscript,gsfonts,gzip,latex-xcolor,libbusiness-creditcard-perl,libcache-cache-perl,libcache-simple-timedexpiry-perl,libchart-perl,libclass-container-perl,libclass-data-inheritable-perl,libclass-returnvalue-perl,libcolor-scheme-perl,libcompress-zlib-perl,libconvert-binhex-perl,libcrypt-passwdmd5-perl,libcrypt-ssleay-perl,libcss-squish-perl,libdate-manip-perl,libdbd-mysql-perl,libdbd-pg-perl,libdbi-perl,libdbix-dbschema-perl,libdbix-searchbuilder-perl,libdevel-stacktrace-perl,libdevel-symdump-perl,liberror-perl,libexcel-writer-xlsx-perl,libexception-class-perl,libfile-counterfile-perl,libfile-rsync-perl,libfont-afm-perl,libfreezethaw-perl,libfrontier-rpc-perl,libgd-gd2-perl,libgd-graph-perl,libhtml-format-perl,libhtml-mason-perl,libhtml-parser-perl,libhtml-scrubber-perl,libhtml-tagset-perl,libhtml-tree-perl,libhtml-widgets-selectlayers-perl,libio-stringy-perl,libipc-run-perl,libipc-run3-perl,libipc-sharelite-perl,libjavascript-rpc-perl,libjson-perl,liblingua-en-inflect-perl,liblingua-en-nameparse-perl,liblocale-gettext-perl,liblocale-maketext-fuzzy-perl,liblocale-maketext-lexicon-perl,liblocale-subcountry-perl,liblog-dispatch-perl,libmailtools-perl,libmime-tools-perl,libmodule-versions-report-perl,libnet-daemon-perl,libnet-ping-external-perl,libnet-scp-perl,libnet-ssh-perl,libnet-whois-raw-perl,libnetaddr-ip-perl,libnumber-format-perl,libpam-modules,libpam-runtime,libpaper-utils,libparams-validate-perl,libparse-recdescent-perl,libpg-perl,libregexp-common-perl,libspreadsheet-writeexcel-perl,libstring-approx-perl,libstring-shellquote-perl,libterm-readkey-perl,libtest-inline-perl,libtext-autoformat-perl,libtext-charwidth-perl,libtext-csv-perl,libtext-iconv-perl,libtext-quoted-perl,libtext-reform-perl,libtext-template-perl,libtext-wrapi18n-perl,libtext-wrapper-perl,libtie-ixhash-perl,libtime-duration-perl,libtime-modules-perl,libtimedate-perl,libtree-simple-perl,libuniversal-require-perl,liburi-perl,libwant-perl,libwww-perl,libxml-parser-perl,libyaml-perl,lmodern,locales,lpr,mime-support,ntp,ntpdate,perl,perl-base,perl-modules,postgresql,postgresql-client,psutils,rsync,openssh-client,texlive,texlive-latex-extra,texinfo,ttf-bitstream-vera,fonts-dustin,libdatetime-perl,libdatetime-format-strptime-perl,libfile-slurp-perl,libspreadsheet-parseexcel-perl,libauthen-passphrase-perl,libnet-domain-tld-perl,libbusiness-us-usps-webtools-perl,libxml-simple-perl,libemail-sender-perl,libemail-sender-transport-smtp-tls-perl,libemail-sender-perl,libemail-sender-transport-smtp-tls-perl,libhtml-defang-perl,libdatetime-format-natural-perl,libcgi-pm-perl,libfile-sharedir-perl,libmodule-versions-report-perl,libtext-wikiformat-perl,libnet-server-perl,libhttp-server-simple-perl,libhtml-rewriteattributes-perl,libmime-types-perl,libperlio-eol-perl,libgnupg-interface-perl,libdata-ical-perl,libcalendar-simple-perl,libdatetime-set-perl,libhook-lexwrap-perl,libhttp-server-simple-mason-perl,libxml-rss-perl,libipc-run-safehandles-perl,libpoe-perl,libsoap-lite-perl,libhtml-tableextract-perl,libhtml-element-extended-perl,libcam-pdf-perl,libnet-openssh-perl,libgd-barcode-perl,sam2p,libsys-sigaction-perl,libgeo-googleearth-pluggable-perl,libgeo-coder-googlev3-perl,libnet-snmp-perl,libcrypt-openssl-rsa-perl,libregexp-common-perl,libnet-cidr-perl,libregexp-ipv6-perl,libhtml-quoted-perl,libtext-password-pronounceable-perl,libconvert-color-perl,liburi-perl,libhtml-rewriteattributes-perl,libregexp-common-net-cidr-perl,liblog-dispatch-perl,libdbix-searchbuilder-perl,libencode-perl,libhtml-mason-psgihandler-perl,libdate-simple-perl,libsnmp-perl,libemail-valid-perl,libio-string-perl,libnet-smtp-ssl-perl,libgeo-coder-googlev3-perl,libcam-pdf-perl,libnet-openssh-perl,libhtml-quoted-perl,libregexp-ipv6-perl,libregexp-common-net-cidr-perl,libencode-perl,libexcel-writer-xlsx-perl,libgeo-googleearth-pluggable-perl,libhtml-mason-psgihandler-perl,libdate-simple-perl,libsnmp-perl,libemail-valid-perl,libparse-fixedlength-perl,libregexp-common-net-cidr-perl,libio-string-perl,libnet-mac-vendor-perl,libnet-https-any-perl
 Suggests: libbusiness-onlinepayment-perl
 Description: Libraries for Freeside billing and trouble ticketing
  Freeside is a web-based billing and trouble ticketing application.
diff --git a/debian/freeside-webui.links b/debian/freeside-webui.links
deleted file mode 100644 (file)
index be5749a..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-etc/freeside/apache2/freeside-base2.conf etc/apache2/conf.d/freeside-base2.conf
-etc/freeside/apache2/freeside-rt.conf    etc/apache2/conf.d/freeside-rt.conf
-
index 85f6a61..e3476d6 100755 (executable)
@@ -46,14 +46,13 @@ export DIST_CONF = ${FREESIDE_CONF}/default_conf
 #export MASON_HANDLER          = /usr/share/freeside/handler.pl
 export MASON_HANDLER=$(TMP)-webui/usr/local/etc/freeside/handler.pl
 
-#export FREESIDE_DOCUMENT_ROOT = /usr/share/freeside/www
-export APACHE_DOCUMENT_ROOT := $(shell [ ${APACHE_VERSION} = '2.4' ] && echo '/var/www/html' || echo '/var/www')
+export APACHE_VERSION        := $(shell /usr/sbin/apache2 -v | grep -q '\/2\.4\.' && echo '2.4' || echo '2')
+export APACHE_DOCUMENT_ROOT  := $(shell [ ${APACHE_VERSION} = '2.4' ] && echo '/var/www/html' || echo '/var/www')
 export FREESIDE_DOCUMENT_ROOT = $(TMP)$(APACHE_DOCUMENT_ROOT)/freeside
 export INIT_FILE              = $(TMP).init
 export INIT_INSTALL           = /bin/true
 export HTTPD_RESTART          = /bin/true
-#export APACHE_CONF           = /etc/apache2/conf.d
-export APACHE_CONF            = $(TMP)-webui/etc/freeside/apache2
+export APACHE_CONF           := $(shell [ ${APACHE_VERSION} = '2.4' ] && echo "$(TMP)-webui/etc/apache2/conf-available" || echo "$(TMP)-webui/etc/apache2/conf.d")
 export FREESIDE_RESTART       = /bin/true
 
 #XXX root?
@@ -180,7 +179,7 @@ install-stamp: build-stamp
 
        perl -p -i -e "\
          s'${TMP}(-webui)?''g;\
-        "  $(TMP)-webui/etc/freeside/apache2/*
+        " $(APACHE_CONF)/*
 
        # Install init
        $(MAKE) -e DESTDIR=$(TMP) install-init
index 03a3f04..21bc920 100644 (file)
@@ -82,6 +82,7 @@ $socket .= '.'.$tag if defined $tag && length($tag);
   'provision_phone'           => 'MyAccount/provision_phone',
   'provision_pbx'             => 'MyAccount/provision_pbx',
   'provision_external'        => 'MyAccount/provision_external',
+  'provision_forward'         => 'MyAccount/provision_forward',
   'unprovision_svc'           => 'MyAccount/unprovision_svc',
   'myaccount_passwd'          => 'MyAccount/myaccount_passwd',
   'reset_passwd'              => 'MyAccount/reset_passwd',
index 7528051..ffc44ec 100644 (file)
@@ -3,24 +3,34 @@
 
 <%=
   if ( @invoices ) {
-    $OUT .= '<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#eeeeee">'.
-            '<TR><TH BGCOLOR="#ff6666" COLSPAN=4>All Invoices</TH></TR>';
-    my $col1 = "ffffff";
-    my $col2 = "dddddd";
+    my $th  = q!<TH STYLE="background: #ff9999; text-align: left; padding: .1em .5em;">!;
+    my $thr = q!<TH STYLE="background: #ff9999; text-align: right; padding: .1em .5em;">!;
+    $OUT .= '<TABLE STYLE="border: 0;" CELLSPACING="0">'.
+            '<TR>'.$th.'Invoice #</TH>'.$th.'Date</TH>'.$thr.'Charges</TH>'
+            .$th.'Date Paid</TH>'.$thr.'Owed</TH></TR>';
+    my $col1 = "#ffffff";
+    my $col2 = "#dddddd";
     my $col = $col1;
 
     foreach my $invoice ( @invoices ) {
-      my $td = qq!<TD BGCOLOR="#$col">!;
+      my $td  = qq!<TD STYLE="background: $col; padding: .1em .5em;">!;
+      my $tdr = qq!<TD STYLE="background: $col; padding: .1em .5em; text-align: right;">!;
       my $a=qq!<A HREF="${url}view_invoice;invnum=!. $invoice->{'invnum'}. '">';
       $OUT .=
-        "<TR>$td${a}Invoice #". $invoice->{'invnum'}. "</A></TD>$td&nbsp;</TD>".
-        "$td$a". $invoice->{'date'}. "</A></TD>$td</TD>".
+        "<TR>".
+        $td . $a . $invoice->{'invnum'}. "</A></TD>" .
+        $td . $a . $invoice->{'date'} . "</A></TD>" .
+        $tdr . $a . $money_char . $invoice->{'charged'} . "</A></TD>" .
+        $td . $a . $invoice->{'lastpay'} . "</A></TD>" .
+        $tdr . $a . $money_char . $invoice->{'owed'} . "</A></TD>" .
         '</TR>';
       $col = $col eq $col1 ? $col2 : $col1;
     }
+    my $tht = '<TH COLSPAN="4" STYLE="background: #ff9999; padding: .1em .5em; text-align: right;">';
+    $OUT .= '<TR>'.$tht.'BALANCE DUE</TH>'.$tht.$money_char.$balance.'</TH></TR>';
     $OUT .= '</TABLE><BR>';
   } else {
-    $OUT .= 'You have no invoices.<BR><BR>';
+    $OUT .= '<P>You have no invoices.</P>';
   }
 %>
 
index 66e2c69..309021a 100644 (file)
@@ -28,28 +28,34 @@ Hello <%= $name %>!<BR><BR>
 } %>
 <%=
   if ( @open_invoices ) {
-    $OUT .= '<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#eeeeee">'.
-            '<TR><TH BGCOLOR="#ff6666" COLSPAN=5>Open Invoices</TH></TR>';
-    my $link = qq!<A HREF="<%= $url %>myaccount!;
-    my $col1 = $stripe1_bgcolor || '#ffffff';
-    my $col2 = $stripe2_bgcolor || '#dddddd';
+    my $th  = q!<TH STYLE="background: #ff9999; text-align: left; padding: .1em .5em;">!;
+    my $thr = q!<TH STYLE="background: #ff9999; text-align: right; padding: .1em .5em;">!;
+    $OUT .= '<TABLE STYLE="border: 0;" CELLSPACING="0">'.
+            '<TR><TH BGCOLOR="#ff6666" COLSPAN="4">Open Invoices</TH></TR>'.
+            '<TR>'.$th.'Invoice #</TH>'.$th.'Date</TH>'.$thr.'Charges</TH>'
+            .$thr.'Owed</TH></TR>';
+    my $col1 = "#ffffff";
+    my $col2 = "#dddddd";
     my $col = $col1;
 
     foreach my $invoice ( @open_invoices ) {
-      my $td = qq!<TD BGCOLOR="$col">!;
+      my $td  = qq!<TD STYLE="background: $col; padding: .1em .5em;">!;
+      my $tdr = qq!<TD STYLE="background: $col; padding: .1em .5em; text-align: right;">!;
       my $a=qq!<A HREF="${url}view_invoice;invnum=!. $invoice->{'invnum'}. '">';
       $OUT .=
-        "<TR>$td${a}Invoice #". $invoice->{'invnum'}. "</A></TD>$td</TD>".
-        "$td$a". $invoice->{'date'}. "</A></TD>$td</TD>".
-        qq!<TD BGCOLOR="$col" ALIGN="right">$a\$!. $invoice->{'owed'}.
-          '</A></TD>'.
+        "<TR>".
+        $td . $a . $invoice->{'invnum'}. "</A></TD>" .
+        $td . $a . $invoice->{'date'} . "</A></TD>" .
+        $tdr . $a . $money_char . $invoice->{'charged'} . "</A></TD>" .
+        $tdr . $a . $money_char . $invoice->{'owed'} . "</A></TD>" .
         '</TR>';
       $col = $col eq $col1 ? $col2 : $col1;
     }
     $OUT .= '</TABLE><BR>';
   } else {
-    $OUT .= 'You have no outstanding invoices.<BR><BR>';
+    $OUT .= '<P>You have no outstanding invoices.</P>';
   }
+
 %>
 
 <%=
index 4802178..999c62d 100644 (file)
@@ -28,7 +28,7 @@ my %payby_mode;
 # $payby_mode{FOO} is true if FOO is thirdparty, false if it's B::OP,
 # nonexistent if it's not supported
 
-if ( $balance > 0 ) { #XXXFIXME "enable selfservice prepay features" flag or something, eventually per-pkg or something really fancy
+if ( ($balance || 0) > 0 ) { #XXXFIXME "enable selfservice prepay features" flag or something, eventually per-pkg or something really fancy
 
   if ( exists( $payby_mode{CARD} ) ) {
     push @menu, { title  => 'Recharge my account with a credit card',
index b558337..086c79e 100644 (file)
@@ -96,7 +96,7 @@ foreach my $pkg (
             '('. $part_svc->{'num_avail'}. ' available)'.
             '</TD></TR>'
       #self-service only supports these services so far
-      if grep { $part_svc->{'svcdb'} eq $_ } qw( svc_acct svc_external );
+      if grep { $part_svc->{'svcdb'} eq $_ } qw( svc_acct svc_external svc_forward );
 
     $col = $col eq $col1 ? $col2 : $col1;
   }
diff --git a/fs_selfservice/FS-SelfService/cgi/provision_svc_forward.html b/fs_selfservice/FS-SelfService/cgi/provision_svc_forward.html
new file mode 100644 (file)
index 0000000..4700663
--- /dev/null
@@ -0,0 +1,29 @@
+<%= include('header', 'Setup forward') %>
+
+<FORM name="OneTrueForm" action="<%= $selfurl %>" METHOD="POST">
+<INPUT TYPE="hidden" NAME="action" VALUE="process_svc_forward">
+
+<%= '<P><B>' . $error . '</B></P>' if $error %>
+
+<%=
+my $out = mason_comp(
+  'session_id' => $session_id,
+  'comp'       => '/edit/elements/svc_forward.html',
+  'args'       => [
+    'svcnum'  => $svcnum,
+    'pkgnum'  => $pkgnum,
+    'svcpart' => $svcpart,
+    'srcsvc'  => $srcsvc,
+    'dstsvc'  => $dstsvc,
+    'src'     => $src,
+    'dst'     => $dst,
+    'email'   => \%forward_emails,
+  ]
+);
+$out->{'output'} || $out->{'error'};
+%>
+
+<INPUT TYPE="submit" VALUE="Setup">
+</FORM>
+
+<%= include('footer') %>
index d039cfd..1b728e2 100755 (executable)
@@ -15,7 +15,7 @@ use FS::SelfService qw(
   access_info login_info login customer_info edit_info invoice
   payment_info process_payment realtime_collect process_prepay
   list_pkgs order_pkg signup_info order_recharge
-  part_svc_info provision_acct provision_external provision_phone
+  part_svc_info provision_acct provision_external provision_phone provision_forward
   unprovision_svc change_pkg suspend_pkg domainselector
   list_svcs list_svc_usage list_cdr_usage list_support_usage
   myaccount_passwd list_invoices create_ticket get_ticket did_report
@@ -70,6 +70,7 @@ my @actions = ( qw(
   process_svc_acct
   process_svc_phone
   process_svc_external
+  process_svc_forward
   delete_svc
   view_usage
   view_usage_details
@@ -847,7 +848,7 @@ sub provision_svc {
 
   my $result = part_svc_info(
     'session_id' => $session_id,
-    map { $_ => $cgi->param($_) } qw( pkgnum svcpart svcnum ),
+    map { $_ => ($cgi->param($_) || '') } qw( pkgnum svcpart svcnum ),
   );
   die $result->{'error'} if exists $result->{'error'} && $result->{'error'};
 
@@ -933,6 +934,33 @@ sub process_svc_external {
   );
 }
 
+sub process_svc_forward {
+
+  my $result = provision_forward (
+    'session_id' => $session_id,
+    map { $_ => $cgi->param($_) || '' } qw(
+      pkgnum svcpart srcsvc src dstsvc dst )
+  );
+
+  if ( exists $result->{'error'} && $result->{'error'} ) { 
+    #warn "$result $result->{'error'}"; 
+    $action = 'provision_svc_forward';
+    return {
+      $cgi->Vars,
+      %{ part_svc_info( 'session_id' => $session_id,
+                        map { $_ => $cgi->param($_) } qw( svcnum pkgnum svcpart )
+                      )
+      },
+      'error' => $result->{'error'},
+    };
+  } else {
+    #just go to setup services page, results will be visible there
+    $action = 'provision';
+    return provision();
+  }
+
+}
+
 sub delete_svc {
   unprovision_svc(
     'session_id' => $session_id,
@@ -1143,7 +1171,7 @@ package FS::SelfService::_selfservicecgi;
 
 use HTML::Entities;
 use FS::SelfService qw(
-    regionselector popselector domainselector location_form didselector
+    regionselector popselector domainselector location_form didselector mason_comp
 );
 
 #false laziness w/agent.cgi
index 0388c2f..27afe12 100644 (file)
@@ -47,7 +47,9 @@
     $OUT .= '</TD><TD ALIGN="right">';
     $OUT .= Date::Format::time2str('%T%P %a&nbsp;%b&nbsp;%o&nbsp;%Y', $usage->{'acctstarttime'});
     $OUT .= '</TD><TD ALIGN="right">';
-    my $duration =  $usage->{'acctstoptime'} - $usage->{'acctstarttime'};
+    my $duration = ( $usage->{'acctstoptime'} && $usage->{'acctstarttime'} )
+                     ? ( $usage->{'acctstoptime'} - $usage->{'acctstarttime'} )
+                     : 0;
     $total += $duration;
     my $h = int($duration/3600);
     my $m = sprintf("%02d", int(($duration % 3600) / 60));
index 0d36853..ec5f321 100755 (executable)
@@ -61,6 +61,8 @@ function part_export_areyousure(href) {
 
     <TH COLSPAN=2 CLASS="grid" BGCOLOR="#cccccc">Modifier</TH>
 
+    <TH CLASS="grid" BGCOLOR="#cccccc" STYLE="font-size: smaller;">Required</TH>
+
   </TR>
 % my $conf = FS::Conf->new;
 % foreach my $part_svc ( @part_svc ) {
@@ -78,6 +80,9 @@ function part_export_areyousure(href) {
 %                $col->columnflag || ( $col->columnlabel !~ /^\S*$/
 %                                      && $col->columnlabel ne $def->{'label'}
 %                                    )
+%                                 || ( $col->required
+%                                      && !$def->{'required'}
+%                                    )
 %              )
 %            }
 %            @dfields ;
@@ -150,7 +155,7 @@ function part_export_areyousure(href) {
     </TD>
 
 %     unless ( @fields ) {
-%       for ( 1..4 ) {  
+%       for ( 1..5 ) {  
          <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"</TD>
 %       }
 %     }
@@ -170,7 +175,6 @@ function part_export_areyousure(href) {
      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $field %></TD>
      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $label %></TD>
      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $flag{$flag} %></TD>
-
      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
 % my $value = &$formatter($part_svc->part_svc_column($field)->columnvalue);
 % if ( $flag =~ /^[MAH]$/ ) { 
@@ -189,6 +193,11 @@ function part_export_areyousure(href) {
 % }
 
      </TD>
+     <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% if ($part_svc_column->required) {
+       Yes
+% }
+     </TD>
 %     $n1="</TR><TR>";
 %     } #foreach $field
 %   if ( $part_svc->restrict_edit_password ) {
index c80cdae..c9eceb4 100644 (file)
@@ -395,6 +395,7 @@ Example:
 %                      'value' => $opt{curr_value},
 %                      'label' => $label,
 %                      'noinit' => $f->{noinit},
+%           'required' => $f->{'required'},
 %              }
 %      );
 %     }
index 2bb4f5e..d950032 100644 (file)
@@ -77,6 +77,7 @@ that field.
     <TH BGCOLOR="#cccccc">Field</TH>
     <TH BGCOLOR="#cccccc">Label</TH>
     <TH BGCOLOR="#cccccc" COLSPAN=2>Modifier</TH>
+    <TH BGCOLOR="#cccccc"><% $manual_require ? 'Required?' : '' %></TH>
   </TR>
 % $part_svc->set('svcpart' => $opt{'clone'}) if $opt{'clone'}; # for now
 % my $i = 0;
@@ -210,9 +211,19 @@ that field.
       &>
 %   }
     </TD>
+    <TD>
+%   if ($manual_require && 
+%       (!$def->{'type'} || !(grep {$_ eq $def->{'type'}} ('checkbox','disabled')))
+%   ) {
+      <INPUT ID="<% $name.'_required' %>" TYPE="checkbox" NAME="<% $svcdb %>__<% $field %>_required" VALUE="Y" 
+        <% ($part_svc_column->required || $def->{'required'}) ? 'CHECKED' : '' %> 
+        <% $def->{'required'} ? 'DISABLED' : '' %>
+       >
+%   }
+    </TD>
   </TR>
   <TR CLASS="row<%$i%>">
-    <TD COLSPAN=2 CLASS="def_info">
+    <TD COLSPAN=3 CLASS="def_info">
 %   if ( $def->{def_info} ) {
       (<% $def->{def_info} %>)
     </TD>
@@ -228,7 +239,7 @@ that field.
     <TD COLSPAN=3 ALIGN="right">
       <% emt('Require "Provision" access right to edit password') %>
     </TD>
-    <TD>
+    <TD COLSPAN=2>
       <INPUT TYPE="checkbox" NAME="restrict_edit_password" VALUE="Y" \
       <% $part_svc->restrict_edit_password ? 'CHECKED' : '' %>>
     </TD>
@@ -244,7 +255,7 @@ that field.
     <TD COLSPAN=3 ALIGN="right">
       <% emt('This service has an attached router') %>
     </TD>
-    <TD>
+    <TD COLSPAN=2>
       <INPUT TYPE="checkbox" NAME="has_router" VALUE="Y" \
       <% $part_svc->has_router ? 'CHECKED' : '' %>>
     </TD>
@@ -301,4 +312,5 @@ if ( $svcdb eq 'svc_acct'
 }
 
 my @defs = map { FS::part_svc->svc_table_fields($svcdb)->{$_} } @fields;
+my $manual_require = FS::part_svc->svc_table_info($svcdb)->{'manual_require'};
 </%init>
index 97b630f..a4e345e 100644 (file)
                    my $columndef = $part_svc->part_svc_column($f->{'field'});
                    my $flag = $columndef->columnflag;
 
+                   $f->{'required'} = 1
+                     if $columndef->required;
+
                    if ( $flag eq 'F' ) { #fixed
                      $f->{'value'} = $columndef->columnvalue;
                      if (length($columndef->columnvalue)) {
diff --git a/httemplate/edit/elements/svc_forward.html b/httemplate/edit/elements/svc_forward.html
new file mode 100644 (file)
index 0000000..e40ebe8
--- /dev/null
@@ -0,0 +1,83 @@
+<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $opt{'svcnum'} %>">
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $opt{'pkgnum'} %>">
+<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $opt{'svcpart'} %>">
+
+<SCRIPT TYPE="text/javascript">
+function srcchanged(what) {
+  if ( what.options[what.selectedIndex].value == 0 ) {
+    what.form.src.disabled = false;
+    what.form.src.style.backgroundColor = "white";
+  } else {
+    what.form.src.disabled = true;
+    what.form.src.style.backgroundColor = "lightgrey";
+  }
+}
+function dstchanged(what) {
+  if ( what.options[what.selectedIndex].value == 0 ) {
+    what.form.dst.disabled = false;
+    what.form.dst.style.backgroundColor = "white";
+  } else {
+    what.form.dst.disabled = true;
+    what.form.dst.style.backgroundColor = "lightgrey";
+  }
+}
+</SCRIPT>
+
+<% ntable("#cccccc",2) %>
+
+<TR>
+  <TD ALIGN="right">Email to</TD>
+  <TD>
+%   if ( $conf->exists('svc_forward-no_srcsvc') ) {
+      <INPUT NAME="srcsrc" TYPE="hidden" VALUE="0">
+%   } else {
+      <SELECT NAME="srcsvc" SIZE=1 onChange="srcchanged(this)">
+%       foreach my $somesvc (keys %email) { 
+          <OPTION VALUE="<% $somesvc %>"
+                  <% $somesvc eq $opt{'srcsvc'} ? 'SELECTED' : '' %>
+          ><% $email{$somesvc} %></OPTION>
+%       } 
+        <OPTION VALUE="0" <% $opt{'src'} ? 'SELECTED' : '' %>
+        >(other email address)</OPTION>
+      </SELECT>
+%   }
+
+%   my $src_disabled =    $opt{'src'}
+%                      || $conf->exists('svc_forward-no_srcsvc')
+%                      || !scalar(%email);
+    <INPUT NAME  = "src"
+           TYPE  = "text"
+           VALUE = "<% $opt{'src'} %>"
+           <% $src_disabled ? '' : 'DISABLED STYLE="background-color: lightgrey"' %>
+    >
+
+  </TD>
+</TR>
+
+<TR><TD ALIGN="right">Forwards to</TD>
+<TD><SELECT NAME="dstsvc" SIZE=1 onChange="dstchanged(this)">
+% foreach my $somesvc (keys %email) { 
+
+  <OPTION<% $somesvc eq $opt{'dstsvc'} ? " SELECTED" : "" %> VALUE="<% $somesvc %>"><% $email{$somesvc} %></OPTION>
+% } 
+
+<OPTION <% $opt{'dst'} ? 'SELECTED' : '' %> VALUE="0">(other email address)</OPTION>
+</SELECT>
+<INPUT TYPE="text" NAME="dst" VALUE="<% $opt{'dst'} %>" <% ( $opt{'dst'} || !scalar(%email) ) ? '' : 'DISABLED STYLE="background-color: lightgrey"' %>>
+</TD></TR>
+</TABLE>
+
+<%init>
+
+# This is used directly by selfservice, only use what's passed
+# Do not look up sensitive information here
+
+my %opt = @_;
+
+my $conf = $opt{'conf'};
+$conf ||= new FS::Conf;
+
+my %email;
+%email = %{$opt{'email'}} if ref($opt{'email'}) eq 'HASH';
+
+</%init>
index 47b020c..7a47f15 100755 (executable)
@@ -101,6 +101,15 @@ function flag_changed(obj) {
       }
     }
   }
+  var required = document.getElementById(layer + '__' + field + '_required');
+  if (required && !required.disabledinit) {
+    if (newflag == "F") {
+      required.checked = false;
+      required.disabled = true;
+    } else {
+      required.disabled = false;
+    }
+  }
 }
 
 window.onload = function() {
@@ -111,6 +120,17 @@ window.onload = function() {
       obj.setAttribute('should_be_multiple', true);
     }
   }
+  var inputs = document.getElementsByTagName('INPUT');
+  for(i = 0; i < inputs.length; i++) {
+    var obj = inputs[i];
+    if (obj.type == 'checkbox') {
+      if ( obj.name.match(/_required$/) ) {
+        if ( obj.disabled ) {
+          obj.disabledinit = 1;
+        }
+      }
+    }
+  }
   for(i = 0; i < selects.length; i++) {
     var obj = selects[i];
     if ( obj.name.match(/_flag$/) ) {
index 90f4823..a5e7789 100755 (executable)
@@ -30,8 +30,9 @@
   <TD BGCOLOR="#eeeeee"><% $part_svc->svc %></TD>
 </TR>
 
-<TR>
-  <TD ALIGN="right"><% mt('Username') |h %></TD>
+<% include('/elements/tr-td-label.html',
+     'label'    => mt('Username'),
+     'required' => $part_svc->part_svc_column('username')->required ) %>
 % if ( $svcnum && $conf->exists('svc_acct-no_edit_username') ) {
     <TD BGCOLOR="#eeeeee"><% $svc_acct->username() %></TD>
     <INPUT TYPE="hidden" NAME="username" VALUE="<% $username %>">
 </TR>
 
 %if ( $part_svc->part_svc_column('_password')->columnflag ne 'F' ) {
-<TR>
 % #XXX eventually should require "Edit Password" ACL
-  <TD ALIGN="right"><% mt('Password') |h %></TD>
+<% include('/elements/tr-td-label.html',
+     'label'    => mt('Password'),
+     'required' => $part_svc->part_svc_column('_password')->required ) %>
   <TD>
     <INPUT TYPE="text" ID="clear_password" NAME="clear_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $pmax %>>
     <& /elements/random_pass.html, 'clear_password' &>
@@ -60,8 +62,9 @@
 %if ( $conf->exists('security_phrase') 
 %  && $part_svc->part_svc_column('sec_phrase')->columnflag ne 'F' ) {
 
-  <TR>
-    <TD ALIGN="right"><% mt('Security phrase') |h %></TD>
+<% include('/elements/tr-td-label.html',
+     'label'    => mt('Security phrase'),
+     'required' => $part_svc->part_svc_column('sec_phrase')->required ) %>
     <TD>
       <INPUT TYPE="text" NAME="sec_phrase" VALUE="<% $sec_phrase %>" SIZE=32>
       (<% mt('for forgotten passwords') |h %>)
 %                                                )
 %                );
 
-  <TR>
-    <TD ALIGN="right"><% mt('Domain') |h %></TD>
+<% include('/elements/tr-td-label.html',
+     'label'    => mt('Domain'),
+     'required' => $part_svc->part_svc_column('domsvc')->required ) %>
     <TD>
       <SELECT NAME="domsvc" SIZE=1>
 % foreach my $svcnum (
   <INPUT TYPE="hidden" NAME="popnum" VALUE="<% $popnum %>">
 % } else { 
 
-  <TR>
-    <TD ALIGN="right"><% mt('Access number') |h %></TD>
+<% include('/elements/tr-td-label.html',
+     'label'    => mt('Access number'),
+     'required' => $part_svc->part_svc_column('popnum')->required ) %>
     <TD><% FS::svc_acct_pop::popselector($popnum) %></TD>
   </TR>
 % } 
          'curr_value' => $svc_acct->sectornum,
          #'part_svc'   => $part_svc,
          #'cust_pkg'   => $cust_pkg,
+         'required'   => $part_svc->part_svc_column('sectornum')->required,
     &>
 %} else {
     <INPUT TYPE="hidden" NAME="sectornum" VALUE="<% $svc_acct->sectornum %>">
 %  
 % if ( length($svc_acct->$xid()) ) { 
 
+<% include('/elements/tr-td-label.html',
+     'label'    => uc($xid),
+     'required' => $part_svc->part_svc_column($xid)->required ) %>
       <TR>
         <TD ALIGN="right"><% uc($xid) %></TD>
           <TD BGCOLOR="#eeeeee"><% $svc_acct->$xid() %></TD>
     <INPUT TYPE="hidden" NAME="<% $xid %>" VALUE="<% $svc_acct->$xid() %>">
 % } else { 
   
-    <TR>
-      <TD ALIGN="right"><% uc($xid) %></TD>
+<% include('/elements/tr-td-label.html',
+     'label'    => uc($xid),
+     'required' => $part_svc->part_svc_column($xid)->required ) %>
       <TD>
         <INPUT TYPE="text" NAME="<% $xid %>" SIZE=8 MAXLENGTH=6 VALUE="<% $svc_acct->$xid() %>">
       </TD>
 % } else { 
 
 
-  <TR>
-    <TD ALIGN="right"><% mt('Real Name') |h %></TD>
+<% include('/elements/tr-td-label.html',
+     'label'    => mt('Real Name'),
+     'required' => $part_svc->part_svc_column('finger')->required ) %>
     <TD>
       <INPUT TYPE="text" NAME="finger" VALUE="<% $svc_acct->finger %>">
     </TD>
 % } else {
 
 
-  <TR>
-    <TD ALIGN="right"><% mt('Home directory') |h %></TD>
+<% include('/elements/tr-td-label.html',
+     'label'    => mt('Home directory'),
+     'required' => $part_svc->part_svc_column('dir')->required ) %>
     <TD><INPUT TYPE="text" NAME="dir" VALUE="<% $svc_acct->dir %>"></TD>
   </TR>
 % } 
 % } else { 
 
 
-  <TR>
-    <TD ALIGN="right"><% mt('Shell') |h %></TD>
+<% include('/elements/tr-td-label.html',
+     'label'    => mt('Shell'),
+     'required' => $part_svc->part_svc_column('shell')->required ) %>
     <TD>
       <SELECT NAME="shell" SIZE=1>
 %
 %   # (should we show this if slipip is fixed?)
 <& /elements/tr-select-router_block_ip.html, 
   'object' => $svc_acct,
-  'ip_field' => 'slipip'
+  'ip_field' => 'slipip',
+  'required' => $part_svc->part_svc_column('routernum')->required,
+  'ip_addr_required' => $part_svc->part_svc_column('slipip')->required,
 &>
 % } else {
 %   # don't expose these to the user--they're only useful in the other case
 %   if ( $part_svc->part_svc_column('slipip')->columnflag =~ /^[FA]$/ ) { 
     <INPUT TYPE="hidden" NAME="slipip" VALUE="<% $svc_acct->slipip %>">
 %   } else { 
-    <TR>
-      <TD ALIGN="right"><% mt('IP') |h %></TD>
+<% include('/elements/tr-td-label.html',
+     'label'    => mt('IP'),
+     'required' => $part_svc->part_svc_column('slipip')->required ) %>
       <TD><INPUT TYPE="text" NAME="slipip" VALUE="<% $svc_acct->slipip %>"></TD>
     </TR>
 %   }
 % } 
 
 
-<TR>
-  <TD ALIGN="right"><% mt('RADIUS groups') |h %></TD>
+<% include('/elements/tr-td-label.html',
+     'label'    => mt('RADIUS groups'),
+     'required' => $part_svc->part_svc_column('usergroup')->required ) %>
 % if ( $part_svc_usergroup->columnflag eq 'F' ) { 
     <TD BGCOLOR="#eeeeee"><% join('<BR>', @groupnames) %></TD>
 % } else { 
index 6370a54..370bfb0 100644 (file)
@@ -47,8 +47,9 @@
   <INPUT TYPE="hidden" NAME="quota" VALUE="<% $svc_acct->quota %>">
 % } else {
 %   my $quota_label = $communigate ? 'Mail storage limit' : 'Quota';
-    <TR>
-      <TD ALIGN="right"><% $quota_label %></TD>
+<% include('/elements/tr-td-label.html',
+     'label'    => $quota_label,
+     'required' => $part_svc->part_svc_column('quota')->required ) %>
       <TD><INPUT TYPE="text" NAME="quota" VALUE="<% $svc_acct->quota %>"></TD>
     </TR>
 % }
index 1b85460..81c694a 100644 (file)
@@ -102,7 +102,16 @@ END
 my @fields = (
   qw( description speed_down speed_up ),
   { field=>'sectornum', type=>'select-tower_sector', },
-  { field=>'routernum', type=>'select-router_block_ip' },
+  { field=>'routernum', type=>'select-router_block_ip', 
+    include_opt_callback => sub { 
+      my $svc_broadband = shift;
+      my $part_svc = $svc_broadband->part_svc;
+      return () unless $part_svc; #sanity check
+      my $col = $part_svc->part_svc_column('ip_addr');
+      return () unless $col; #sanity check
+      return ('ip_addr_required' => $col->required);
+    },
+  },
   { field=>'mac_addr' , type=>'input-mac_addr' },
   qw(
       latitude longitude altitude
index 73f6465..de56070 100755 (executable)
@@ -6,74 +6,19 @@ Service #<% $svcnum ? "<B>$svcnum</B>" : " (NEW)" %><BR>
 Service: <B><% $part_svc->svc %></B><BR><BR>
 
 <FORM ACTION="process/svc_forward.cgi" METHOD="POST">
-<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>">
-<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
-<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>">
-
-<SCRIPT TYPE="text/javascript">
-function srcchanged(what) {
-  if ( what.options[what.selectedIndex].value == 0 ) {
-    what.form.src.disabled = false;
-    what.form.src.style.backgroundColor = "white";
-  } else {
-    what.form.src.disabled = true;
-    what.form.src.style.backgroundColor = "lightgrey";
-  }
-}
-function dstchanged(what) {
-  if ( what.options[what.selectedIndex].value == 0 ) {
-    what.form.dst.disabled = false;
-    what.form.dst.style.backgroundColor = "white";
-  } else {
-    what.form.dst.disabled = true;
-    what.form.dst.style.backgroundColor = "lightgrey";
-  }
-}
-</SCRIPT>
-
-<% ntable("#cccccc",2) %>
-
-<TR>
-  <TD ALIGN="right">Email to</TD>
-  <TD>
-%   if ( $conf->exists('svc_forward-no_srcsvc') ) {
-      <INPUT NAME="srcsrc" TYPE="hidden" VALUE="0">
-%   } else {
-      <SELECT NAME="srcsvc" SIZE=1 onChange="srcchanged(this)">
-%       foreach $_ (keys %email) { 
-          <OPTION VALUE="<% $_ %>"
-                  <% $_ eq $srcsvc ? 'SELECTED' : '' %>
-          ><% $email{$_} %></OPTION>
-%       } 
-        <OPTION VALUE="0" <% $src ? 'SELECTED' : '' %>
-        >(other email address)</OPTION>
-      </SELECT>
-%   }
-
-%   my $src_disabled =    $src
-%                      || $conf->exists('svc_forward-no_srcsvc')
-%                      || !scalar(%email);
-    <INPUT NAME  = "src"
-           TYPE  = "text"
-           VALUE = "<% $src %>"
-           <% $src_disabled ? '' : 'DISABLED STYLE="background-color: lightgrey"' %>
-    >
-
-  </TD>
-</TR>
-
-<TR><TD ALIGN="right">Forwards to</TD>
-<TD><SELECT NAME="dstsvc" SIZE=1 onChange="dstchanged(this)">
-% foreach $_ (keys %email) { 
-
-  <OPTION<% $_ eq $dstsvc ? " SELECTED" : "" %> VALUE="<% $_ %>"><% $email{$_} %></OPTION>
-% } 
-
-<OPTION <% $dst ? 'SELECTED' : '' %> VALUE="0">(other email address)</OPTION>
-</SELECT>
-<INPUT TYPE="text" NAME="dst" VALUE="<% $dst %>" <% ( $dst || !scalar(%email) ) ? '' : 'DISABLED STYLE="background-color: lightgrey"' %>>
-</TD></TR>
-    </TABLE>
+
+<% include('elements/svc_forward.html',
+     'conf'    => $conf,
+     'svcnum'  => $svcnum,
+     'pkgnum'  => $pkgnum,
+     'svcpart' => $svcpart,
+     'srcsvc'  => $srcsvc,
+     'dstsvc'  => $dstsvc,
+     'src'     => $src,
+     'dst'     => $dst,
+     'email'   => \%email,
+   ) %>
+
 <BR><INPUT TYPE="submit" VALUE="Submit">
 </FORM>
 
@@ -136,44 +81,23 @@ my $action = $svc_forward->svcnum ? 'Edit' : 'Add';
 
 my %email;
 
-#starting with those currently attached
-foreach my $method (qw( srcsvc_acct dstsvc_acct )) {
-  my $svc_acct = $svc_forward->$method();
-  $email{$svc_acct->svcnum} = $svc_acct->email if $svc_acct;
-}
-
 if ($pkgnum) {
 
   #find all possible user svcnums (and emails)
 
-  #and including the rest for this customer
-  my($u_part_svc,@u_acct_svcparts);
-  foreach $u_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_acct'}) ) {
-    push @u_acct_svcparts,$u_part_svc->getfield('svcpart');
-  }
-
-  my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
-  my($custnum)=$cust_pkg->getfield('custnum');
-  my($i_cust_pkg);
-  foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) {
-    my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum');
-    my($acct_svcpart);
-    foreach $acct_svcpart (@u_acct_svcparts) {   #now find the corresponding 
-                                              #record(s) in cust_svc ( for this
-                                              #pkgnum ! )
-      foreach my $i_cust_svc (
-        qsearch( 'cust_svc', { 'pkgnum'  => $cust_pkgnum,
-                               'svcpart' => $acct_svcpart } )
-      ) {
-        my $svc_acct =
-          qsearchs( 'svc_acct', { 'svcnum' => $i_cust_svc->svcnum } );
-        $email{$svc_acct->svcnum} = $svc_acct->email;
-      }  
-    }
-  }
+  my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+  die "Specified package not found" unless $cust_pkg;
+  %email = $cust_pkg->forward_emails('svc_forward' => $svc_forward);
 
 } elsif ( $action eq 'Add' ) {
+
   die "\$action eq Add, but \$pkgnum is null!\n";
+
+} else {
+
+  use FS::cust_pkg;
+  %email = FS::cust_pkg->forward_emails('svc_forward' => $svc_forward);
+
 }
 
 my($srcsvc,$dstsvc,$dst)=(
diff --git a/httemplate/elements/make_appointment.html b/httemplate/elements/make_appointment.html
new file mode 100644 (file)
index 0000000..ddf377f
--- /dev/null
@@ -0,0 +1,39 @@
+<& /elements/header-popup.html &>
+
+% #XXX actually, the action needs to repace the top-level window with this
+% #$params->{'action'} = $p. 'rt/Search/Schedule.html?'. $query;
+<FORM NAME="appointment_form" METHOD="POST" ACTION="<% $p %>elements/schedule-appointment.html">
+
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $cgi->param('custnum') |h %>">
+
+% my @sched_item = qsearch('sched_item', { 'disabled' => '', });
+% my @username = map $_->access_user->username, @sched_item;
+% foreach my $username (@username) { 
+  <INPUT TYPE="hidden" NAME="username" VALUE="<% $username |h %>">
+% }
+
+Length: 
+<SELECT NAME="LengthMin">
+%  for ( my $hours = .5; $hours < 10.5; $hours += .5 ) {
+%    my $min = $hours * 60;
+     <OPTION VALUE="<% $min %>"
+             <% $min == $default_length ? 'SELECTED' : '' %>
+     ><% $hours %> hour<% $hours > 1 ? 's' : '' %>
+%  }
+</SELECT>
+<BR>
+<BR>
+
+<INPUT TYPE="submit" VALUE="Schedule appointment">
+
+</FORM>
+
+<& /elements/footer-popup.html &>
+<%init>
+
+my $default_length = FS::Conf->new->config('default_appointment_length') || 180;
+
+#XXX '&pkgnum=$pkgnum";need to be for specific package/location, not just for a customer... default to active(/ordered) package in a pkg_class w/ticketing_queueid, otherwise, a popup?  (well, we're already a popup now) we're getting complicated like form-creat_ticket.html
+
+
+</%init>
index 1bab4ef..34bba7e 100644 (file)
@@ -328,13 +328,14 @@ tie my %report_bill_event, 'Tie::IxHash',
 ;
 
 tie my %report_payments, 'Tie::IxHash',
-    'Payments' => [ $fsurl.'search/report_cust_pay.html', 'Payment report (by type and/or date range)' ],
+  'Payments' => [ $fsurl.'search/report_cust_pay.html', 'Payment report (by type and/or date range)' ],
+  'Payment application detail' => [ $fsurl.'search/report_cust_bill_pay_pkg.html', 'Line item application detail' ],
 ;
 $report_payments{'Pending Payments'} = [ $fsurl.'search/cust_pay_pending.html?magic=_date;statusNOT=done', 'Pending real-time payments' ]
   if $curuser->access_right('View customer pending payments');
+$report_payments{'Unapplied Payments'} = [ $fsurl.'search/report_cust_pay.html?unapplied=1', 'Unapplied payment report (by type and/or date range)' ];
 $report_payments{'Voided Payments'} = [ $fsurl.'search/report_cust_pay.html?void=1', 'Voided payment report (by type and/or date range)' ]
   if $curuser->access_right('View customer pending payments');
-$report_payments{'Unapplied Payments'} = [ $fsurl.'search/report_cust_pay.html?unapplied=1', 'Unapplied payment report (by type and/or date range)' ];
 $report_payments{'Payment Batches'} = [ $fsurl.'search/pay_batch.html', 'Payment batches (by status and/or date range)' ]
   if $conf->exists('batch-enable') || $conf->config('batch-enable_payby');
 $report_payments{'Unapplied Payment Aging'} = [ $fsurl.'search/report_unapplied_cust_pay.html', 'Unapplied payment aging report' ];
diff --git a/httemplate/elements/popup_link-make_appointment.html b/httemplate/elements/popup_link-make_appointment.html
new file mode 100644 (file)
index 0000000..60a337c
--- /dev/null
@@ -0,0 +1,31 @@
+<%doc>
+
+Example:
+
+  include('/elements/init_overlib.html')
+
+  include( '/elements/popup_link-make-appointment.html', { #hashref or a list, either way
+             'custnum' => 54,
+  })
+
+</%doc>
+<& /elements/popup_link.html, $params &>\
+<%init>
+
+my $params = { 'closetext' => 'Close' };
+
+if (ref($_[0]) eq 'HASH') {
+  $params = { %$params, %{ $_[0] } };
+} else {
+  $params = { %$params, @_ };
+}
+
+#$params->{'label'} ||= 'schedule new appointment';
+$params->{'label'} ||= 'Schedule new appointment';
+$params->{'actionlabel'} ||= 'Schedule appointment';
+#$params->{'width'} ||= 350;
+#$params->{'height'} ||= 220;
+
+$params->{'action'} = $p. '/elements/make_appointment.html?custnum='. $params->{'custnum'};
+
+</%init>
diff --git a/httemplate/elements/schedule-appointment.html b/httemplate/elements/schedule-appointment.html
new file mode 100644 (file)
index 0000000..45a8a5b
--- /dev/null
@@ -0,0 +1,20 @@
+<& /elements/header-popup.html &>
+<h1>Scheduling appointment...</h1>
+<SCRIPT TYPE="text/javascript">
+  window.top.location.href = '<% $url %>';
+</SCRIPT>
+<& /elements/footer-popup.html &>
+<%init>
+
+my @username = map encode_entities($_), $cgi->param('username');
+my $LengthMin = encode_entities( $cgi->param('LengthMin') );
+my $custnum = encode_entities( $cgi->param('custnum') );
+
+my $query = join('&', map "username=$_", @username).
+            "&LengthMin=$LengthMin".
+            "&custnum=$custnum";
+            #XXX '&pkgnum=$pkgnum";need to be for specific package/location, not just for a customer... default to active(/ordered) package in a pkg_class w/ticketing_queueid, otherwise, a popup?  we're getting complicated like form-creat_ticket.html
+
+my $url = $p. 'rt/Search/Schedule.html?'. $query;
+
+</%init>
index ff98551..f2a570b 100644 (file)
@@ -13,6 +13,7 @@ Example:
           'usedatetime' => 1, #use DateTime->strftime to format the date
                               # instead of Date::Format->time2str
           'noinit'      => 1,          #first one on the page is enough
+          'required'    => 1,
        },
   &>
 
@@ -24,8 +25,9 @@ Example:
 <SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar-setup.js"></SCRIPT>
 % }
 
-<TR>
-  <TD ALIGN="right"><% $label %></TD>
+<% include('/elements/tr-td-label.html',
+     'label'    => $label,
+     'required' => $required ) %>
   <TD COLSPAN=<% $colspan %>>
     <INPUT TYPE="text" NAME="<% $name %>" ID="<% $name %>_text" VALUE="<% $value %>">
     <IMG SRC="<%$fsurl%>images/calendar.png" ID="<% $name  %>_button" STYLE="cursor: pointer" TITLE="<% mt('Select date') |h %>">
@@ -48,7 +50,7 @@ Example:
 
 <%init>
 
-my($name, $value, $label, $format, $usedatetime, $noinit, $colspan);
+my($name, $value, $label, $format, $usedatetime, $noinit, $colspan, $required);
 if ( ref($_[0]) ) {
   my $opt = shift;
   $name        = $opt->{'name'};
@@ -58,6 +60,7 @@ if ( ref($_[0]) ) {
   $usedatetime = $opt->{'usedatetime'};
   $noinit      = $opt->{'noinit'};
   $colspan     = $opt->{'colspan'} || 1;
+  $required    = $opt->{'required'};
 } else {
   ($name, $value, $label, $format, $usedatetime) = @_;
   $colspan = 1;
index c306641..b51afc0 100644 (file)
@@ -1,5 +1,6 @@
-<TR>
-  <TD ALIGN="right"><% $opt{'label'} || 'Device type: ' %></TD>
+<% include('/elements/tr-td-label.html',
+     'label'    => $opt{'label'} || 'Device type: ',
+     'required' => $opt{'required'} ) %>
   <TD><% include('select-hardware_type.html', %opt) %></TD>
 </TR>
 
index 11f7c48..ee13568 100644 (file)
@@ -30,7 +30,7 @@ function clearhint_ip_addr (what) {
     what.value = '';
 }
 </script>
-<& /elements/tr-td-label.html, label => ($opt{'label'} || 'Router') &>
+<& /elements/tr-td-label.html, label => ($opt{'label'} || 'Router'), required => $opt{'required'} &>
 <td>
   <& /elements/select-tiered.html, prefix => 'router_', tiers => [
   {
@@ -56,9 +56,9 @@ function clearhint_ip_addr (what) {
 ]
 &>
 </td></tr>
-<& /elements/tr-td-label.html, label => 'IP address' &>
+<& /elements/tr-td-label.html, label => 'IP address', required => $opt{'ip_addr_required'} &>
 <td>
-% warn Dumper \%fixed;
+% #warn Dumper \%fixed;
 % if ( exists $fixed{$ip_field} ) {
   <input type="hidden" id="input_ip_addr" name="<% $ip_field %>" 
     value="<% $opt{'ip_addr'} |h%>"><% $opt{'ip_addr'} || '' %>
index 12fca20..a48aa01 100644 (file)
        'process-display'    => scalar($conf->config('manual_process-display')),
        'process-skip_first' => $conf->exists('manual_process-skip_first'),
        'num_payments'       => scalar($cust_main->cust_pay), 
-       'surcharge_percentage' => scalar($conf->config('credit-card-surcharge-percentage')),
+       'surcharge_percentage' =>
+         ( $payby eq 'CARD'
+             ? scalar($conf->config('credit-card-surcharge-percentage'))
+             : 0
+         ),
   &>
 
 % if ( $conf->exists('part_pkg-term_discounts') ) {
index 778799b..e57913e 100644 (file)
@@ -289,11 +289,11 @@ my $qsearch = join(' AND ', @qsearch);
 $qsearch = ( scalar(keys %$hashref) ? ' AND ' : ' WHERE ' ) . $qsearch
   if $qsearch;
 
-my $query =     {   'table'     => 'cdr',
-                    'hashref'   => $hashref,
-                               'extra_sql' => $qsearch,
-                               'order_by'  => 'ORDER BY calldate',
-                };
+my $query = { 'table'     => 'cdr',
+              'hashref'   => $hashref,
+              'extra_sql' => $qsearch,
+              'order_by'  => 'ORDER BY calldate',
+            };
 
 if ( $cgi->param('longest') =~ /^(\d+)$/ && $cgi->param('longest') > 0 ) {
     $cgi->param('maxrecords',$1);
@@ -308,16 +308,26 @@ if ( $cgi->param('longest') =~ /^(\d+)$/ && $cgi->param('longest') > 0 ) {
 ###
 
 my %header = %{ FS::cdr->table_info->{'fields'} };
+$header{ratename} = 'Rate plan';
 
 my @first = qw( acctid calldate clid charged_party src dst dcontext );
 my %first = map { $_=>1 } @first;
 
-my @fields = ( @first, grep !$first{$_}, fields('cdr') );
+my @fields = ( @first,
+               grep !$first{$_}, ( fields('cdr'), 'ratename' )
+             );
 
 if ( $cgi->param('show') ) {
   @fields = grep $cgi->param("show_$_"), @fields;
 }
 
+if ( grep { $_ eq 'ratename' } @fields ) {
+  $query->{addl_from} .= '
+  LEFT JOIN rate_detail ON (cdr.rated_ratedetailnum = rate_detail.ratedetailnum)
+  LEFT JOIN rate        ON (rate_detail.ratenum = rate.ratenum)
+  ';
+}
+
 my @header = map {
                    if ( exists($header{$_}) ) {
                      $header{$_};
diff --git a/httemplate/search/cust_bill_pay_pkg.html b/httemplate/search/cust_bill_pay_pkg.html
new file mode 100644 (file)
index 0000000..a6738f3
--- /dev/null
@@ -0,0 +1,572 @@
+<& elements/search.html,
+              'title'         => 'Payment application detail', #to line item
+              'name_singular' => 'payment application',
+              'query'         => $query,
+              'count_query'   => $count_query,
+               'count_addl'   => [ $money_char. '%.2f total', ],
+               'header'       => [
+                   #'#',
+
+                   'Amount',
+                   'By',
+                   '#',
+
+                   #payment
+                   'Date',
+                   'By',
+
+                   #application
+                   'Applied',
+
+                   # line item
+                   'Description',
+                   'Location',
+                   @post_desc_header,
+
+                   #invoice
+                   'Invoice',
+                   'Date',
+                   FS::UI::Web::cust_header(),
+               ],
+               'fields'       => [
+                   #'billpaypkgnum',
+                   sub { sprintf($money_char.'%.2f', shift->paid ) },
+                   sub { $payby{shift->cust_bill_pay->cust_pay->payby} },
+                   sub { my $cust_pay = shift->cust_bill_pay->cust_pay;
+                         $cust_pay->payby =~ /^(CARD|CHEK)$/
+                           ? $cust_pay->paymask : $cust_pay->payinfo;
+                       },
+
+                   sub { time2str('%b %d %Y', shift->get('cust_pay_date') ) },
+                   sub { shift->cust_bill_pay->cust_pay->otaker },
+
+                   sub { sprintf($money_char.'%.2f', shift->amount ) },
+
+                   sub { $_[0]->pkgnum > 0
+                           ? $_[0]->get('pkg')      # possibly use override.pkg
+                           : $_[0]->get('itemdesc') # but i think this correct
+                       },
+                   $location_sub,
+                   @post_desc,
+                   'invnum',
+                   sub { time2str('%b %d %Y', shift->_date ) },
+                   \&FS::UI::Web::cust_fields,
+               ],
+               'sort_fields'  => [
+                   'paid',
+                   '', #payby
+                   'cust_pay_date',
+                   '', #'otaker',
+                   '', #amount
+                   '', #line item description
+                   '', #location
+                   @post_desc_null,
+                   'invnum',
+                   '_date',
+                   #cust fields
+               ],
+               'links' => [
+                   '',
+                   '',
+                   '',
+                   '',
+                   '',
+                   '',
+                   '',
+                   '',
+                   @post_desc_null,
+                   $ilink,
+                   $ilink,
+                   ( map { $_ ne 'Cust. Status' ? $clink : '' }
+                         FS::UI::Web::cust_header()
+                   ),
+               ],
+               'align' => 'rcrrlrlllrrcl'.
+                          $post_desc_align.
+                          'rr'.
+                          FS::UI::Web::cust_aligns(),
+               'color' => [ 
+                              '',
+                              '',
+                              '',
+                              '',
+                              '',
+                              '',
+                              '',
+                              '',
+                              @post_desc_null,
+                              '',
+                              '',
+                              FS::UI::Web::cust_colors(),
+                            ],
+               'style' => [ 
+                              '',
+                              '',
+                              '',
+                              '',
+                              '',
+                              '',
+                              '',
+                              '',
+                              @post_desc_null,
+                              '',
+                              '',
+                              FS::UI::Web::cust_styles(),
+                          ],
+           
+&>
+<%init>
+
+#(and now we're cust_bill_pay_pkg.html, false laziness w/cust_credit_bill_pkg)
+#LOTS of false laziness below w/cust_bill_pkg.cgi
+# and a little w/cust_credit.html
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $conf = new FS::Conf;
+
+my %payby = FS::payby->payby2shortname;
+
+#here is the agent virtualization
+my $agentnums_sql =
+  $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' );
+
+my @where = ( $agentnums_sql );
+
+if ( $cgi->param('usernum') =~ /^(\d+)$/ ) {
+  push @where, "cust_pay.usernum = $1";
+}
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+push @where, "cust_bill._date >= $beginning",
+             "cust_bill._date <= $ending";
+
+my($cr_begin, $cr_end) = FS::UI::Web::parse_beginning_ending($cgi, 'payment');
+push @where, "cust_pay._date >= $cr_begin",
+             "cust_pay._date <= $cr_end";
+
+#payment amount?  seems more what is expected than the applied amount
+my @lt_gt = FS::UI::Web::parse_lt_gt($cgi, 'paid' );
+s/paid/cust_pay.paid/g foreach (@lt_gt);
+push @where, @lt_gt;
+
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+  push @where, "cust_main.agentnum = $1";
+}
+
+if ( $cgi->param('billpkgnum') =~ /^(\d+)$/ ) {
+  push @where, "billpkgnum = $1";
+}
+
+#classnum
+# not specified: all classes
+# 0: empty class
+# N: classnum
+my $use_override = $cgi->param('use_override');
+if ( $cgi->param('classnum') =~ /^(\d+)$/ ) {
+  my $comparison = '';
+  if ( $1 == 0 ) {
+    $comparison = "IS NULL";
+  } else {
+    $comparison = "= $1";
+  }
+
+  if ( $use_override ) {
+    push @where, "(
+      part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
+      override.classnum $comparison AND pkgpart_override IS NOT NULL
+    )";
+  } else {
+    push @where, "part_pkg.classnum $comparison";
+  }
+}
+
+if ( $cgi->param('taxclass')
+     && ! $cgi->param('istax')  #no part_pkg.taxclass in this case
+                                #(should we save a taxclass or a link to taxnum
+                                # in cust_bill_pkg or something like
+                                # cust_bill_pkg_tax_location?)
+   )
+{
+
+  #override taxclass when use_override is specified?  probably
+  #if ( $use_override ) {
+  #
+  #  push @where,
+  #    ' ( '. join(' OR ',
+  #                  map {
+  #                        ' (    part_pkg.taxclass = '. dbh->quote($_).
+  #                        '      AND pkgpart_override IS NULL '.
+  #                        '   OR '.
+  #                        '      override.taxclass = '. dbh->quote($_).
+  #                        '      AND pkgpart_override IS NOT NULL '.
+  #                        ' ) '
+  #                      }
+  #                      $cgi->param('taxclass')
+  #               ).
+  #    ' ) ';
+  #
+  #} else {
+
+    push @where,
+      ' ( '. join(' OR ',
+                    map ' part_pkg.taxclass = '.dbh->quote($_),
+                        $cgi->param('taxclass')
+                 ).
+      ' ) ';
+
+  #}
+
+}
+
+my @loc_param = qw( district city county state country );
+
+if ( $cgi->param('out') ) {
+
+  my ( $loc_sql, @param ) = FS::cust_pkg->location_sql( 'ornull' => 1 );
+  while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution
+    $loc_sql =~ s/\?/'cust_main_county.'.shift(@param)/e;
+  }
+
+  $loc_sql =~ s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g
+    if $cgi->param('istax');
+
+  push @where, "
+    0 = (
+          SELECT COUNT(*) FROM cust_main_county
+           WHERE cust_main_county.tax > 0
+             AND $loc_sql
+        )
+  ";
+
+  #not linked to by anything, but useful for debugging "out of taxable region"
+  if ( grep $cgi->param($_), @loc_param ) {
+
+    my %ph = map { $_ => dbh->quote( scalar($cgi->param($_)) ) } @loc_param;
+
+    my ( $loc_sql, @param ) = FS::cust_pkg->location_sql;
+    while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution
+      $loc_sql =~ s/\?/$ph{shift(@param)}/e;
+    }
+
+    push @where, $loc_sql;
+
+  }
+
+} elsif ( $cgi->param('country') ) {
+
+  my @counties = $cgi->param('county');
+   
+  if ( scalar(@counties) > 1 ) {
+
+    #hacky, could be more efficient.  care if it is ever used for more than the
+    # tax-report_groups filtering kludge
+
+    my $locs_sql =
+      ' ( '. join(' OR ', map {
+
+          my %ph = ( 'county' => dbh->quote($_),
+                     map { $_ => dbh->quote( $cgi->param($_) ) }
+                       qw( district city state country )
+                   );
+
+          my ( $loc_sql, @param ) = FS::cust_pkg->location_sql;
+          while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution
+            $loc_sql =~ s/\?/$ph{shift(@param)}/e;
+          }
+
+          $loc_sql;
+
+        } @counties
+
+      ). ' ) ';
+
+    push @where, $locs_sql;
+
+  } else {
+
+    my %ph = map { $_ => dbh->quote( scalar($cgi->param($_)) ) } @loc_param;
+
+    my ( $loc_sql, @param ) = FS::cust_pkg->location_sql;
+    while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution
+      $loc_sql =~ s/\?/$ph{shift(@param)}/e;
+    }
+
+    push @where, $loc_sql;
+
+  }
+   
+  my($title, $name);
+  if ( $cgi->param('istax') ) {
+    $title = 'Tax credits';
+    $name  = 'tax credits';
+    if ( $cgi->param('taxname') ) {
+      push @where, 'itemdesc = '. dbh->quote( $cgi->param('taxname') );
+    #} elsif ( $cgi->param('taxnameNULL') {
+    } else {
+      push @where, "( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )";
+    }
+  } elsif ( $cgi->param('nottax') ) {
+    $title = 'Payment applications to line items';
+    $name  = 'applications';
+    #what can we usefully do with "taxname" ????  look up a class???
+  } else {
+    $title = 'Payment applications to line items';
+    $name  = 'applications';
+    #warn "neither nottax nor istax parameters specified";
+  }
+
+  if ( $cgi->param('taxclassNULL')
+       && ! $cgi->param('istax')  #no taxclass in this case
+     )
+  {
+
+    my %hash = ( 'country' => scalar($cgi->param('country')) );
+    foreach (qw( state county )) {
+      $hash{$_} = scalar($cgi->param($_)) if $cgi->param($_);
+    }
+    my $cust_main_county = qsearchs('cust_main_county', \%hash);
+    die "unknown base region for empty taxclass" unless $cust_main_county;
+
+    my $same_sql = $cust_main_county->sql_taxclass_sameregion;
+    push @where, $same_sql if $same_sql;
+
+  }
+
+} elsif ( scalar( grep( /locationtaxid/, $cgi->param ) ) ) {
+
+  # this should really be shoved out to FS::cust_pkg->location_sql or something
+  # along with the code in report_newtax.cgi
+
+  my %pn = (
+   'county'        => 'tax_rate_location.county',
+   'state'         => 'tax_rate_location.state',
+   'city'          => 'tax_rate_location.city',
+   'locationtaxid' => 'cust_bill_pkg_tax_rate_location.locationtaxid',
+  );
+
+  my %ph = map { ( $pn{$_} => dbh->quote( $cgi->param($_) || '' ) ) }
+           qw( city county state locationtaxid );
+
+  push @where,
+    join( ' AND ', map { "( $_ = $ph{$_} OR $ph{$_} = '' AND $_ IS NULL)" }
+                   keys %ph
+    );
+
+}
+
+if ( $cgi->param('itemdesc') ) {
+  if ( $cgi->param('itemdesc') eq 'Tax' ) {
+    push @where, "(itemdesc='Tax' OR itemdesc is null)";
+  } else {
+    push @where, 'itemdesc='. dbh->quote($cgi->param('itemdesc'));
+  }
+}
+
+if ( $cgi->param('report_group') =~ /^(=|!=) (.*)$/ && $cgi->param('istax') ) {
+  my ( $group_op, $group_value ) = ( $1, $2 );
+  if ( $group_op eq '=' ) {
+    #push @where, 'itemdesc LIKE '. dbh->quote($group_value.'%');
+    push @where, 'itemdesc = '. dbh->quote($group_value);
+  } elsif ( $group_op eq '!=' ) {
+    push @where, '( itemdesc != '. dbh->quote($group_value) .' OR itemdesc IS NULL )';
+  } else {
+    die "guru meditation #00de: group_op $group_op\n";
+  }
+  
+}
+
+push @where, 'cust_bill_pkg.pkgnum != 0' if $cgi->param('nottax');
+push @where, 'cust_bill_pkg.pkgnum  = 0' if $cgi->param('istax');
+
+if ( $cgi->param('cust_tax') ) {
+  #false laziness -ish w/report_tax.cgi
+  my $cust_exempt;
+  if ( $cgi->param('taxname') ) {
+    my $q_taxname = dbh->quote($cgi->param('taxname'));
+    $cust_exempt =
+      "( tax = 'Y'
+         OR EXISTS ( SELECT 1 FROM cust_main_exemption
+                       WHERE cust_main_exemption.custnum = cust_main.custnum
+                         AND cust_main_exemption.taxname = $q_taxname )
+       )
+      ";
+  } else {
+    $cust_exempt = " tax = 'Y' ";
+  }
+
+  push @where, $cust_exempt;
+}
+
+my $count_query = "SELECT COUNT(DISTINCT billpaypkgnum),
+                          SUM(cust_bill_pay_pkg.amount)";
+
+my $join_cust =
+  '      JOIN cust_bill ON ( cust_bill_pkg.invnum = cust_bill.invnum )'.
+  FS::UI::Web::join_cust_main('cust_bill', 'cust_pkg');
+
+
+my $join_pkg;
+
+my $join_cust_bill_pkg = 'LEFT JOIN cust_bill_pkg USING ( billpkgnum )';
+
+if ( $cgi->param('nottax') ) {
+
+  $join_pkg =  ' LEFT JOIN cust_pkg USING ( pkgnum )
+                 LEFT JOIN part_pkg USING ( pkgpart )
+                 LEFT JOIN part_pkg AS override
+                   ON pkgpart_override = override.pkgpart ';
+  $join_pkg .= ' LEFT JOIN cust_location USING ( locationnum ) '
+    if $conf->exists('tax-pkg_address');
+
+} elsif ( $cgi->param('istax') ) {
+
+  #false laziness w/report_tax.cgi $taxfromwhere
+  if ( scalar( grep( /locationtaxid/, $cgi->param ) ) ||
+            $cgi->param('iscredit') eq 'rate') {
+
+    $join_pkg .=
+      ' LEFT JOIN cust_bill_pkg_tax_rate_location USING ( billpkgnum ';
+    if ( $cgi->param('iscredit') eq 'rate' ) {
+      $join_pkg .= ', billpkgtaxratelocationnum )';
+    } elsif ( $conf->exists('tax-pkg_address') ) {
+      $join_pkg .= ', billpkgtaxlocationnum )';
+      push @where, "billpkgtaxratelocationnum IS NULL";
+    } else {
+      $join_pkg .= ' )';
+      push @where, "billpkgtaxratelocationnum IS NULL";
+    }
+
+    $join_pkg .= ' LEFT JOIN tax_rate_location USING ( taxratelocationnum ) ';
+
+  } elsif ( $conf->exists('tax-pkg_address') ) {
+
+    $join_pkg .= ' LEFT JOIN cust_bill_pkg_tax_location USING ( billpkgnum )
+                   LEFT JOIN cust_location              USING ( locationnum ) ';
+
+    #quelle kludge, somewhat false laziness w/report_tax.cgi
+    s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g for @where;
+  }
+
+} else {
+
+  #warn "neither nottax nor istax parameters specified";
+  #same as before?
+  $join_pkg =  ' LEFT JOIN cust_pkg USING ( pkgnum )
+                 LEFT JOIN part_pkg USING ( pkgpart ) ';
+
+}
+
+my $where = ' WHERE '. join(' AND ', @where);
+
+my $join_pay = ' LEFT JOIN cust_bill_pay USING ( billpaynum )
+                 LEFT JOIN cust_pay      USING ( paynum ) ';
+
+$count_query .= " FROM cust_bill_pay_pkg
+                         $join_cust_bill_pkg
+                         $join_pkg
+                         $join_pay
+                         $join_cust
+                       $where";
+
+my @select = ( 'cust_bill_pay_pkg.*',
+               'cust_bill_pkg.*',
+               'cust_pay.paid',
+               'cust_pay.otaker',
+               'cust_pay._date AS cust_pay_date',
+               'cust_bill._date',
+             );
+push @select, 'part_pkg.pkg' unless $cgi->param('istax');
+push @select, 'cust_main.custnum',
+              FS::UI::Web::cust_sql_fields();
+
+my @post_desc_header = ();
+my @post_desc = ();
+my @post_desc_null = ();
+my $post_desc_align = '';
+if ( $conf->exists('enable_taxclasses') && ! $cgi->param('istax') ) {
+  push @post_desc_header, 'Tax class';
+  push @post_desc, 'taxclass';
+  push @post_desc_null, '';
+  $post_desc_align .= 'l';
+  push @select, 'part_pkg.taxclass'; # or should this use override?
+}
+
+warn "$join_cust_bill_pkg
+                  $join_pkg
+                  $join_pay
+                  $join_cust";
+
+my $query = {
+  'table'     => 'cust_bill_pay_pkg',
+  'addl_from' => "$join_cust_bill_pkg
+                  $join_pkg
+                  $join_pay
+                  $join_cust",
+  'hashref'   => {},
+  'select'    => join(', ', @select ),
+  'extra_sql' => $where,
+  'order_by'  => 'ORDER BY billpaypkgnum', #cust_bill. or cust_pay._date?
+};
+
+my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ];
+my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $tax_pkg_address = $conf->exists('tax-pkg_address');
+my $tax_ship_address = $conf->exists('tax-ship_address');
+
+my $location_sub = sub {
+  #my $cust_bill_pay_pkg = shift;
+  my $self = shift;
+  my $tax_Xlocation = $self->cust_bill_pkg_tax_Xlocation;
+  if ( defined($tax_Xlocation) && $tax_Xlocation ) {
+
+    if ( ref($tax_Xlocation) eq 'FS::cust_bill_pkg_tax_location' ) {
+
+      if ( $tax_Xlocation->taxtype eq 'FS::cust_main_county' ) {
+        my $cust_main_county = $tax_Xlocation->cust_main_county;
+        if ( $cust_main_county ) {
+          $cust_main_county->label;
+        } else {
+          ''; #cust_main_county record is gone... history?  yuck.
+        }
+      } else {
+        '(CCH tax_rate)'; #XXX FS::tax_rate.. vendor taxes not yet handled here
+      }
+
+    } elsif ( ref($tax_Xlocation) eq 'FS::cust_bill_pkg_tax_rate_location' ) {
+      '(CCH)'; #XXX vendor taxes not yet handled here
+    } else {
+      'unknown tax_Xlocation '. ref($tax_Xlocation);
+    }
+
+  } else {
+
+    my $cust_bill_pkg = $self->cust_bill_pkg;
+    if ( $cust_bill_pkg->pkgnum > 0 ) {
+      my $cust_pkg = $cust_bill_pkg->cust_pkg;
+      if ( $tax_pkg_address && (my $cust_location = $cust_pkg->cust_location) ){
+        $cust_location->county_state_country;
+      } else {
+        my $cust_main = $cust_pkg->cust_main;
+        if ( $tax_ship_address && $cust_main->has_ship_address ) {
+          $cust_main->county_state_country('ship_');
+        } else {
+          $cust_main->county_state_country;
+        }
+      }
+
+    } else {
+      #tax?  we shouldn't have wound up here then...
+      ''; #return customer ship or bill address? (depending on tax-ship_address)
+    }
+
+  }
+
+};
+
+</%init>
index 0eefd4c..ef54478 100644 (file)
@@ -188,7 +188,9 @@ die "access denied"
   unless $FS::CurrentUser::CurrentUser->access_right('List rating data');
 
 my @fields = fields('cdr');
+push @fields, 'ratename';
 my $labels = FS::cdr->table_info->{'fields'};
+$labels->{ratename} = 'Rate plan';
 
 my $conf = new FS::Conf;
 my $default_phone_countrycode =
diff --git a/httemplate/search/report_cust_bill_pay_pkg.html b/httemplate/search/report_cust_bill_pay_pkg.html
new file mode 100644 (file)
index 0000000..2347bab
--- /dev/null
@@ -0,0 +1,88 @@
+<& /elements/header.html, mt('Payment application report') &>
+
+<FORM ACTION="cust_bill_pay_pkg.html" METHOD="GET">
+<!--<INPUT TYPE="hidden" NAME="magic" VALUE="_date">-->
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+<& /elements/tr-select-user.html,
+              'label'       => emt('Employee: '),
+              'access_user' => $access_user,
+&>
+
+<& /elements/tr-select-agent.html,
+     curr_value    => scalar( $cgi->param('agentnum') ),
+     #label         => emt('Line items for agent: '),
+     disable_empty => 0,
+&>
+
+<!--
+<& /elements/tr-select-cust_main-status.html,
+     label => emt('Customer status'),
+&>
+-->
+
+<!-- customer
+<& /elements/tr-select-cust_class.html,
+     'label'        => emt('Class'),
+     'multiple'     => 1,
+     'pre_options'  => [ '' => emt('(none)') ],
+     'all_selected' => 1,
+&>
+-->
+
+<!-- some sort of label saying this is the payment date... -->
+<& /elements/tr-input-beginning_ending.html,
+     'prefix' => 'payment',
+&>
+
+<& /elements/tr-input-lessthan_greaterthan.html,
+     label   => emt('Amount'),
+     field   => 'paid',
+&>
+
+<!--
+<TR>
+  <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="nottax" VALUE="Y" onClick="nottax_changed(this)" onChange="nottax_change(thid)"></TD>
+  <TD><% mt('Omit taxes') |h %></TD>
+</TD>
+
+<TR>
+  <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="istax" VALUE="Y" onClick="istax_changed(this)" onChange="istax_change(thid)"></TD>
+  <TD><% mt('Taxes only') |h %></TD>
+</TD>
+
+<SCRIPT TYPE="text/javascript">
+  function nottax_changed (what) {
+    if (what.checked && what.form.istax.checked) {
+      what.form.istax.checked = false;
+    }
+  }
+  function istax_changed (what) {
+    if (what.checked && what.form.nottax.checked) {
+      what.form.nottax.checked = false;
+    }
+  }
+</SCRIPT>
+-->
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>">
+
+</FORM>
+
+<& /elements/footer.html &>
+<%init>
+
+#Financial reports?
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $access_user = $FS::CurrentUser::CurrentUser->access_users_hashref('table' => 'cust_pay');
+
+my $conf = new FS::Conf;
+
+</%init>
+
index f106087..eb7df68 100644 (file)
@@ -93,7 +93,7 @@ if ( $db eq 'PostgreSQL' ) {
                 && FS::Record->scalar_sql('SHOW archive_mode') eq 'on';
 
   my $slave = 
-    FS::Record->scalar_sql('SHOW archive_command') =~ / postgres\@([\w\.\-]): /
+    FS::Record->scalar_sql('SHOW archive_command') =~ / postgres\@([\w\.\-]+): /
       ? $1 : '';
 
   $status{'Replication'} = [
index 9bec7b9..335bd20 100644 (file)
@@ -2,16 +2,7 @@
 <A NAME="appointments"><FONT CLASS="fsinnerbox-title">Appointments</FONT></A>
 
 % if ( $FS::CurrentUser::CurrentUser->access_right('Make appointment') ) { 
-%# just a link for now,  should probably be FS::TicketSystem->href_schedule
-%# maybe need to add a dropdown for type (install etc.) from a custom field
-%# or maybe change to a popup and get that info there
-% my @sched_item = qsearch('sched_item', { 'disabled' => '', });
-% my @username = map $_->access_user->username, @sched_item;
-% my $query = join('&', map "username=$_", @username).
-%             '&LengthMin=180'. #XXX appointment length
-%             '&custnum='. $cust_main->custnum; #.
-%             #XXX '&pkgnum=$pkgnum";need to be for specific package/location, not just for a customer... default to active(/ordered) package in a pkg_class w/ticketing_queueid, otherwise, a popup?  we're getting complicated like form-creat_ticket.html
-<A HREF="<%$p%>rt/Search/Schedule.html?<% $query %>">Make appointment</A>
+  <& /elements/popup_link-make_appointment.html, custnum=>$cust_main->custnum &>
 % }
 
 %#  |
index d7644b7..39055f4 100644 (file)
@@ -10,6 +10,7 @@ function clearhint_search_cust_svc(obj, str) {
 }
     </SCRIPT>
 
+%  my $showdidlink = browse_received_did_inventory($cust_pkg->custnum);
 %  foreach my $part_svc (
 %    $cust_pkg->part_svc(
 %      'summarize_size'    => $opt{'cust_pkg-large_pkg_size'},
@@ -97,7 +98,7 @@ function clearhint_search_cust_svc(obj, str) {
 %             local $opt{'num_avail'} = $part_svc->num_avail;
               <BR><% svc_provision_link($cust_pkg, $part_svc, \%opt, $curuser) %>
 %           }
-%           if (browse_received_did_inventory($cust_pkg->custnum)) {
+%           if ($showdidlink) {
               <BR><A HREF="<%$p%>browse/did_order.html?custnum=<%$cust_pkg->custnum%>"><% mt('Browse Received DID Inventory') |h %></A> 
 %           }
 %       }
index 690f858..047abda 100644 (file)
 %           }
 %           if ( $curuser->access_right('Unsuspend customer package') ) { 
 %             if ( $cust_pkg->order_date eq $cust_pkg->get('susp') ) { #on hold
-                (&nbsp;<% pkg_link('misc/unsusp_pkg', emt('Start bililng now'), $cust_pkg) %>&nbsp;)
+                (&nbsp;<% pkg_link('misc/unsusp_pkg', emt('Start billing now'), $cust_pkg) %>&nbsp;)
 %             } else {
                 (&nbsp;<% pkg_unsuspend_link($cust_pkg) %>&nbsp;)
                 (&nbsp;<% pkg_resume_link($cust_pkg) %>&nbsp;)
index 540f1f8..62b6562 100644 (file)
@@ -30,7 +30,7 @@ if ( $error ) {
     <TD>
       <INPUT TYPE="text" NAME="username" VALUE="<? echo htmlspecialchars($username); ?>"><? if ( $single_domain ) { echo '@'.$single_domain; } ?>
     </TD>
-<  /TR>
+  </TR>
 
   <INPUT TYPE="hidden" NAME="domain" VALUE="<? echo $single_domain ?>">
 
diff --git a/ng_selfservice/prepaid.php b/ng_selfservice/prepaid.php
new file mode 100644 (file)
index 0000000..bf941f8
--- /dev/null
@@ -0,0 +1,202 @@
+<? $title ='Prepaid Card Account Recharge'; include('elements/header.php'); ?>
+<? $current_menu = 'prepaid.php'; include('elements/menu.php'); ?>
+
+<?
+// This page is currently only designed for packages that use prepaid pricing.
+// Usage limits should be in seconds, and you currently cannot mix packages that have 
+// usage limits with packages that don't.  Payments must be made by prepaid card.
+// The account service must be flagged as primary service of package.
+//
+// You can't change packages if you have a positive balance, but you CAN use 
+// this form to only change package or only use a prepaid card--doing both isn't
+// required.
+
+$prepaid_cardnum = isset($_POST['prepaid_cardnum']) ? $_POST['prepaid_cardnum'] : '';
+$pkgpart = isset($_POST['pkgpart']) ? $_POST['pkgpart'] : '';
+$pkgnum  = isset($_POST['pkgnum']) ? $_POST['pkgnum'] : '';
+$success = '';
+$error   = '';
+
+if ($pkgnum || $pkgpart) {
+  if ($pkgnum && $pkgpart) {
+    $change_results = $freeside->change_pkg(array(
+      'session_id'      => $_COOKIE['session_id'],
+      'pkgpart'         => $pkgpart,
+      'pkgnum'          => $pkgnum,
+    ));
+    if ( isset($change_results['error']) && $change_results['error'] ) {
+      $error = $change_results['error'];
+    } else {
+      $success .= ' Package applied to your account.';
+      $pkgnum = '';
+      $pkgpart = '';
+    }
+  } else if ($pkgnum) {
+    $error = 'No account selected';
+  } else if ($pkgpart) {
+    $error = 'No package selected';
+  }
+}
+
+if ($prepaid_cardnum) {
+  $payment_results = $freeside->process_prepay(array(
+    'session_id'      => $_COOKIE['session_id'],
+    'prepaid_cardnum' => $prepaid_cardnum,
+  ));
+  if ( isset($payment_results['error']) && $payment_results['error'] ) {
+    $error = $payment_results['error'];
+  } else {
+    $success .= ' Prepaid card applied to your account.';
+    $prepaid_cardnum = '';
+  }
+}
+
+$customer_info = $freeside->customer_info_short( array(
+  'session_id' => $_COOKIE['session_id'],
+) );
+if ( isset($customer_info['error']) && $customer_info['error'] ) {
+  $error = $customer_info['error'];
+}
+
+$signup_info = $freeside->signup_info( array('customer_session_id' => $_COOKIE['session_id'], 'keys' => ['part_pkg']) );
+if (isset($signup_info['error']) && $signup_info['error']) {
+  $error = $signup_info['error'];
+}
+
+$list_pkgs = $freeside->list_pkgs( array(
+  'session_id' => $_COOKIE['session_id'],
+) );
+if ( isset($list_pkgs['error']) && $list_pkgs['error'] ) {
+  $error = $list_pkgs['error'];
+}
+
+extract($customer_info);
+extract($signup_info);
+extract($list_pkgs);
+
+$actsvcs = array();
+$expsvcs = array();
+foreach ($cust_pkg as $pkg) {
+  $thissvc = array();
+  $thissvc['svcnum']    = $pkg['primary_cust_svc']['svcnum'];
+  $thissvc['overlimit'] = $pkg['primary_cust_svc']['overlimit'];
+  $thissvc['label']     = $pkg['primary_cust_svc']['label'][1];
+  $thissvc['pkgnum'] = $pkg['pkgnum'];
+  $thissvc['status'] = $pkg['status'];
+  $actsvcs[$thissvc['svcnum']] = $thissvc;
+  if ($thissvc['overlimit'] or ($thissvc['status'] != 'active')) {
+    $expsvcs[$thissvc['svcnum']] = $thissvc;
+  }
+}
+
+if (count($actsvcs) > 0) {
+  $list_svcs = $freeside->list_svcs( array(
+    'session_id' => $_COOKIE['session_id'],
+  ) );
+  if ( isset($list_svcs['error']) && $list_svcs['error'] ) {
+    $error = $list_svcs['error'];
+  }
+  extract($list_svcs);
+  foreach ($svcs as $svc) {
+    if (isset($actsvcs[$svc['svcnum']])) {
+      $actsvcs[$svc['svcnum']]['seconds'] = strlen($svc['seconds']) ? $svc['seconds'] : 'Unlimited';
+    }
+  }
+}
+
+if ($success) {
+  echo '<P><B>' . $success . '</B></P>';
+}
+include('elements/error.php');
+
+if (count($actsvcs) > 0) {
+?>
+<TABLE STYLE="text-align: left;">
+<TR><TH>Account</TH><TH STYLE="text-align: right;">Seconds Remaining</TH></TR>
+<? 
+  foreach ($actsvcs as $svc) {
+    if ($svc['status'] == 'active') {
+      $slabel = $svc['seconds'];
+    } else {
+      $slabel = '<I>' . ucfirst($svc['status']) . '</I>';
+    }  
+?>
+<TR>
+<TD><? echo $svc['label'] ?></TD>
+<TD STYLE="text-align: right;"><? echo $slabel ?></TD>
+</TR>
+<?
+  }
+?>
+</TABLE>
+<?
+}
+if ($balance != 0) {
+  $blabel = ($balance < 0) ? 'Credit' : 'Balance';
+?>
+
+<P><B><? echo $blabel ?>:</B> <? echo $money_char . abs($balance) ?></P>
+
+<?
+}
+?>
+
+<FORM NAME="OneTrueForm" METHOD="POST" ACTION="prepaid.php" onSubmit="document.OneTrueForm.process.disabled=true">
+
+<?
+if ($balance <= 0) {
+  if (count($expsvcs) > 0) {
+?>
+
+<P>
+<B>Select an account to recharge:</B><BR>
+<SELECT NAME="pkgnum">
+<OPTION VALUE=""></OPTION>
+<? foreach ($expsvcs as $svc) { ?>
+<OPTION VALUE="<? echo $svc['pkgnum'] ?>"<? echo $pkgnum == $svc['pkgnum'] ? ' CHECKED' : ''  ?>>
+<?   echo $svc['label'] ?>
+</OPTION>
+<? } ?>
+</SELECT>
+</P>
+
+<P>
+<B>Select a package to add to account</B><BR>
+<SELECT NAME="pkgpart">
+<OPTION VALUE=""></OPTION>
+<? foreach ($part_pkg as $pkg) { ?>
+<OPTION VALUE="<? echo $pkg['pkgpart'] ?>"<? echo $pkgpart == $pkg['pkgpart'] ? ' CHECKED' : ''  ?>>
+<?   echo $pkg['pkg'] . ' - ' . $money_char . $pkg['options']['recur_fee'] ?>
+</OPTION>
+<? } ?>
+</SELECT>
+</P>
+
+<?
+  } else {
+?>
+
+<P>You have no services to recharge at this time.</P>
+
+<?
+  }
+}
+if (($balance > 0) or (count($expsvcs) > 0)) {
+?>
+
+<P>
+<B>Enter prepaid card number:</B><BR>
+<INPUT TYPE="text" NAME="prepaid_cardnum" VALUE="<? echo $prepaid_cardnum ?>">
+</P>
+
+<INPUT TYPE="submit" NAME="submit" VALUE="Submit">
+
+<?
+}
+?>
+
+</FORM>
+
+<? include('elements/menu_footer.php'); ?>
+<? include('elements/footer.php'); ?>
+
diff --git a/rt/lib/RT/Generated.pm b/rt/lib/RT/Generated.pm
deleted file mode 100644 (file)
index 2f46d48..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-# BEGIN BPS TAGGED BLOCK {{{
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
-#                                          <sales@bestpractical.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# END BPS TAGGED BLOCK }}}
-
-package RT;
-use warnings;
-use strict;
-
-our $VERSION = '4.0.22';
-
-
-
-$BasePath = '/opt/rt3';
-$EtcPath = '/opt/rt3/etc';
-$BinPath = '/opt/rt3/bin';
-$SbinPath = '/opt/rt3/sbin';
-$VarPath = '/opt/rt3/var';
-$LexiconPath = '/opt/rt3/share/po';
-$PluginPath = '/opt/rt3/plugins';
-$LocalPath = '/opt/rt3/local';
-$LocalEtcPath = '/opt/rt3/local/etc';
-$LocalLibPath        =    '/opt/rt3/local/lib';
-$LocalLexiconPath = '/opt/rt3/local/po';
-$LocalPluginPath = '/opt/rt3/local/plugins';
-# $MasonComponentRoot is where your rt instance keeps its mason html files
-$MasonComponentRoot = '/var/www/freeside/rt';
-# $MasonLocalComponentRoot is where your rt instance keeps its site-local
-# mason html files.
-$MasonLocalComponentRoot = '/opt/rt3/local/html';
-# $MasonDataDir Where mason keeps its datafiles
-$MasonDataDir = '/usr/local/etc/freeside/masondata';
-# RT needs to put session data (for preserving state between connections
-# via the web interface)
-$MasonSessionDir = '/opt/rt3/var/session_data';
-
-
-1;