Merge branch 'master' of git.freeside.biz:/home/git/freeside
authorIvan Kohler <ivan@freeside.biz>
Mon, 8 Jul 2013 21:09:40 +0000 (14:09 -0700)
committerIvan Kohler <ivan@freeside.biz>
Mon, 8 Jul 2013 21:09:40 +0000 (14:09 -0700)
14 files changed:
FS/FS/Conf.pm
FS/FS/Schema.pm
FS/FS/cdr/netsapiens.pm
FS/FS/cust_pkg.pm
FS/FS/part_pkg.pm
FS/FS/part_pkg/delayed_Mixin.pm
bin/sqlradius-reexport-group [new file with mode: 0644]
httemplate/edit/part_pkg.cgi
httemplate/elements/order_pkg.js
httemplate/elements/select-part_pkg.html
httemplate/elements/tr-select-cust-part_pkg.html
httemplate/misc/cust-part_pkg.cgi
httemplate/misc/order_pkg.html
httemplate/search/customer_accounting_summary.html

index f76c72f..ae1fd4b 100644 (file)
@@ -4349,6 +4349,13 @@ and customer address. Include units.',
   },
 
   {
+    'key'         => 'part_pkg-delay_start',
+    'section'     => '',
+    'description' => 'Enabled "delayed start" option for packages.',
+    'type'        => 'checkbox',
+  },
+
+  {
     'key'         => 'mcp_svcpart',
     'section'     => '',
     'description' => 'Master Control Program svcpart.  Leave this blank.',
index 6df45e2..5e2e2ef 100644 (file)
@@ -2050,6 +2050,7 @@ sub tables_hashref {
         'setup_show_zero',  'char', 'NULL',  1, '', '',
         'successor',     'int',     'NULL', '', '', '',
         'family_pkgpart','int',     'NULL', '', '', '',
+        'delay_start',   'int',     'NULL', '', '', '',
       ],
       'primary_key' => 'pkgpart',
       'unique' => [],
index bcaa349..9d07aef 100644 (file)
@@ -15,11 +15,11 @@ use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker );
   'disabled'      => 0,     #0 default, set to 1 to disable
 
   'import_fields' => [
-   
+
     sub { my ($cdr, $direction) = @_;
-          if ($direction =~ /^o/) { # 'origination'
+          if ($direction =~ /^t/) { # 'origination'
             # leave src and dst as they are
-          } elsif ($direction =~ /^t/) {
+          } elsif ($direction =~ /^o/) {
             my ($local, $remote) = ($cdr->src, $cdr->dst);
             $cdr->set('dst', $local);
             $cdr->set('src', $remote);
@@ -28,7 +28,7 @@ use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker );
     '', #Domain
     '', #user
     'src', #local party (src/dst, based on direction)
-    _cdr_date_parser_maker('startddate'),
+    _cdr_date_parser_maker('startdate'),
     _cdr_date_parser_maker('answerdate'),
     sub { my ($cdr, $duration) = @_;
           $cdr->set('duration', $duration);
@@ -37,14 +37,15 @@ use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker );
             if $cdr->answerdate;
         },
     'dst', #remote party
-    '', #dialed number
+    sub { my ($cdr, $dialednum) = @_;
+        $cdr->set('dst',$dialednum) if $dialednum =~ /^(\+?1)?8(8|([02-7])\3)/;
+        }, #dialed number
     'uniqueid', #CallID (timestamp + '-' +  32 char hex string)
-    'src_ip_addr',
-    'dst_ip_addr',
+    '',
+    '',
     'disposition',
   ],
 
 );
 
 1;
-
index 398dce1..dd67d03 100644 (file)
@@ -289,6 +289,7 @@ sub insert {
 
   my $part_pkg = $self->part_pkg;
 
+  # if the package def says to start only on the first of the month:
   if ( $part_pkg->option('start_1st', 1) && !$self->start_date ) {
     my ($sec,$min,$hour,$mday,$mon,$year) = (localtime(time) )[0,1,2,3,4,5];
     $mon += 1 unless $mday == 1;
@@ -296,6 +297,8 @@ sub insert {
     $self->start_date( timelocal_nocheck(0,0,0,1,$mon,$year) );
   }
 
+  # set up any automatic expire/adjourn/contract_end timers
+  # based on the start date
   foreach my $action ( qw(expire adjourn contract_end) ) {
     my $months = $part_pkg->option("${action}_months",1);
     if($months and !$self->$action) {
@@ -304,16 +307,16 @@ sub insert {
     }
   }
 
+  # if this package has "free days" and delayed setup fee, tehn 
+  # set start date that many days in the future.
+  # (this should have been set in the UI, but enforce it here)
   if (    ! $options{'change'}
        && ( my $free_days = $part_pkg->option('free_days',1) )
        && $part_pkg->option('delay_setup',1)
        #&& ! $self->start_date
      )
   {
-    my ($mday,$mon,$year) = (localtime(time) )[3,4,5];
-    #my $start_date = ($self->start_date || timelocal(0,0,0,$mday,$mon,$year)) + 86400 * $free_days;
-    my $start_date = timelocal(0,0,0,$mday,$mon,$year) + 86400 * $free_days;
-    $self->start_date($start_date);
+    $self->start_date( $part_pkg->default_start_date );
   }
 
   $self->order_date(time);
index 22e8828..0722647 100644 (file)
@@ -5,7 +5,7 @@ use strict;
 use vars qw( %plans $DEBUG $setup_hack $skip_pkg_svc_hack );
 use Carp qw(carp cluck confess);
 use Scalar::Util qw( blessed );
-use Time::Local qw( timelocal_nocheck );
+use Time::Local qw( timelocal timelocal_nocheck );
 use Tie::IxHash;
 use FS::Conf;
 use FS::Record qw( qsearch qsearchs dbh dbdef );
@@ -116,6 +116,8 @@ If this record is not obsolete, will be null.
 ancestor of this record.  If this record is not a successor to another 
 part_pkg, will be equal to pkgpart.
 
+=item delay_start - Number of days to delay package start, by default
+
 =back
 
 =head1 METHODS
@@ -682,6 +684,7 @@ sub check {
        )
     || $self->ut_numbern('fcc_ds0s')
     || $self->ut_numbern('fcc_voip_class')
+    || $self->ut_numbern('delay_start')
     || $self->ut_foreign_keyn('successor', 'part_pkg', 'pkgpart')
     || $self->ut_foreign_keyn('family_pkgpart', 'part_pkg', 'pkgpart')
     || $self->SUPER::check
@@ -1072,9 +1075,39 @@ sub is_free {
   }
 }
 
+# whether the plan allows discounts to be applied to this package
 sub can_discount { 0; }
-
+# whether the plan allows changing the start date
 sub can_start_date { 1; }
+  
+# the default start date; takes an FS::cust_main as an argument
+sub default_start_date {
+  my $self = shift;
+  my $cust_main = shift;
+  my $conf = FS::Conf->new;
+
+  if ( $self->delay_start ) {
+    my $delay = $self->delay_start;
+    
+    my ($mday,$mon,$year) = (localtime(time))[3,4,5];
+    my $start_date = timelocal(0,0,0,$mday,$mon,$year) + 86400 * $delay;
+    return $start_date;
+
+  } elsif ( $conf->exists('order_pkg-no_start_date') ) {
+
+    return '';
+
+  } elsif ( $cust_main ) {
+    
+    return $cust_main->next_bill_date;
+  
+  } else {
+    
+    return '';
+
+  }
+}
 
 sub can_currency_exchange { 0; }
 
index ab53bda..ae286d3 100644 (file)
@@ -2,6 +2,7 @@ package FS::part_pkg::delayed_Mixin;
 
 use strict;
 use vars qw(%info);
+use Time::Local qw(timelocal);
 use NEXT;
 
 %info = (
@@ -52,4 +53,15 @@ sub calc_remain {
 
 sub can_start_date { ! shift->option('delay_setup', 1) }
 
+sub default_start_date {
+  my $self = shift;
+  if ( $self->option('delay_setup') and $self->option('free_days') ) {
+    my $delay = $self->option('free_days');
+
+    my ($mday, $mon, $year) = (localtime(time))[3,4,5];
+    return timelocal(0,0,0,$mday,$mon,$year) + 86400 * $self->option('free_days');
+  }
+  return $self->NEXT::default_start_date(@_);
+}
+
 1;
diff --git a/bin/sqlradius-reexport-group b/bin/sqlradius-reexport-group
new file mode 100644 (file)
index 0000000..a60e471
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/bin/perl
+
+use FS::UID 'adminsuidsetup';
+use FS::Record qw( qsearch );
+use FS::part_export;
+use FS::radius_group;
+
+my ($user, $exportnum, $group) = @ARGV;
+my $dbh = adminsuidsetup($user) or die;
+$FS::UID::AutoCommit = 0;
+my $radius_group;
+if ( $group =~ /^\d+$/ ) {
+  $radius_group = FS::radius_group->by_key($group);
+} else {
+  $radius_group = qsearchs('radius_group',{'groupname' => $group});
+}
+die "no radius group $group" unless $radius_group;
+
+my @attrs = qsearch('radius_attr', {groupnum => $radius_group->groupnum});
+foreach my $attr (@attrs) {
+  print $attr->attrname."\n";
+  my $error = $export->export_attr_insert($attr);
+  die $error if $error;
+}
+
index 89f1615..c13caf5 100755 (executable)
@@ -65,6 +65,7 @@
                    'report_option'    => 'Report classes',
                    'fcc_ds0s'         => 'Voice-grade equivalents',
                    'fcc_voip_class'   => 'Category',
+                   'delay_start'      => 'Default delay (days)',
                  },
 
      'fields' => [
                      { field=>'setup_cost', type=>'money', },
                      { field=>'recur_cost', type=>'money', },
 
+                     ( $conf->exists('part_pkg-delay_start')
+                       ? ( { type  => 'tablebreak-tr-title',
+                             value => 'Delayed start',
+                           },
+                           { field => 'delay_start',
+                             type => 'text', size => 6 },
+                         )
+                       : ()
+                     ),
+
                    { type => 'columnnext' },
 
                      { field    => 'agent_type',
index 1069a0e..762b2dd 100644 (file)
@@ -4,9 +4,15 @@ function pkg_changed () {
 
   if ( form.pkgpart.selectedIndex > 0 ) {
 
+    var opt = form.pkgpart.options[form.pkgpart.selectedIndex];
+    var date_button = document.getElementById('start_date_button');
+    var date_button_disabled = document.getElementById('start_date_button_disabled');
+    var date_text = document.getElementById('start_date_text');
+
+
     form.submitButton.disabled = false;
     if ( discountnum ) {
-      if ( form.pkgpart.options[form.pkgpart.selectedIndex].getAttribute('data-can_discount') == 1 ) {
+      if ( opt.getAttribute('data-can_discount') == 1 ) {
         form.discountnum.disabled = false;
         discountnum_changed(form.discountnum);
       } else {
@@ -15,14 +21,17 @@ function pkg_changed () {
       }
     }
 
-    if ( form.pkgpart.options[form.pkgpart.selectedIndex].getAttribute('data-can_start_date') == 1 ) {
-      form.start_date_text.disabled = false;
-      form.start_date.style.backgroundColor = '#ffffff';
-      form.start_date_button.style.display = '';
+    form.start_date_text.value = opt.getAttribute('data-start_date');
+    if ( opt.getAttribute('data-can_start_date') == 1 ) {
+      date_text.style.backgroundColor = '#ffffff';
+      date_text.disabled = false;
+      date_button.style.display = '';
+      date_button_disabled.style.display = 'none';
     } else {
-      form.start_date_text.disabled = true;
-      form.start_date.style.backgroundColor = '#dddddd';
-      form.start_date_button.style.display = 'none';
+      date_text.style.backgroundColor = '#dddddd';
+      date_text.disabled = true;
+      date_button.style.display = 'none';
+      date_button_disabled.style.display = '';
     }
 
   } else {
index 439c4b5..9d41b07 100644 (file)
@@ -23,7 +23,6 @@ Example:
               'empty_label'    => 'Select package', #should this be the default?
               'label_callback' => sub { shift->pkg_comment },
               'hashref'        => \%hash,
-              'extra_option_attributes' => [ 'can_discount', 'can_start_date' ],
               %opt,
           )
 %>
index 848ab0a..c9c50d2 100644 (file)
@@ -7,10 +7,11 @@
 
   <SCRIPT TYPE="text/javascript">
 
-    function part_pkg_opt(what, value, text, can_discount, can_start_date) {
+    function part_pkg_opt(what, value, text, can_discount, can_start_date, start_date) {
       var optionName = new Option(text, value, false, false);
       optionName.setAttribute('data-can_discount',   can_discount);
       optionName.setAttribute('data-can_start_date', can_start_date);
+      optionName.setAttribute('data-start_date',     start_date || '');
       var length = what.length;
       what.options[length] = optionName;
     }
         // add the new packages
         opt(what.form.pkgpart, '', 'Select package');
         var packagesArray = eval('(' + part_pkg + ')' );
-        for ( var s = 0; s < packagesArray.length; s=s+4 ) {
+        for ( var s = 0; s < packagesArray.length; s=s+5 ) {
+          //surely this should be some kind of JSON structure
           var packagesLabel  = packagesArray[s+1];
           var can_discount   = packagesArray[s+2];
           var can_start_date = packagesArray[s+3];
+          var start_date     = packagesArray[s+4];
           part_pkg_opt(
-            what.form.pkgpart, packagesArray[s], packagesLabel, can_discount, can_start_date
+            what.form.pkgpart, packagesArray[s], packagesLabel, can_discount, can_start_date, start_date
           );
         }
 
                   );
     }
 
+    window.onload = function() {
+      classnum_changed(document.getElementById('classnum'));
+    }
+
   </SCRIPT>
 
   <TR>
index 43b9229..7aebda4 100644 (file)
@@ -5,8 +5,9 @@ my( $custnum, $prospectnum, $classnum ) = $cgi->param('arg');
 
 
 my $agent;
+my $cust_main;
 if ( $custnum ) {
-  my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+  $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
     or die 'unknown custnum';
   $agent = $cust_main->agent;
 } else {
@@ -31,12 +32,18 @@ my @part_pkg = qsearch({
   'order_by'  => 'ORDER BY pkg',
 });
 
-my @return = map  { warn $_->can_start_date;
+my $date_format = FS::Conf->new->config('date_format') || '%m/%d/%Y';
+
+my @return = map  {
+                    my $start_date = $_->default_start_date($cust_main);
+                    $start_date = time2str($date_format, $start_date)
+                      if $start_date;
                     ( $_->pkgpart,
                       $_->pkg_comment,
                       $_->can_discount,
                       $_->can_start_date,
-                    );
+                      $start_date,
+                    )
                   }
                   #sort { $a->pkg_comment cmp $b->pkg_comment }
                   @part_pkg;
index 3973442..a257e53 100644 (file)
     <& /elements/input-date-field.html,{
                 'name'      => 'start_date',
                 'format'    => $date_format,
-                'value'     => $start_date,
+                'value'     => '',
                 'noinit'    => 1,
               } &>
+    <IMG SRC   = "<%$fsurl%>images/calendar-disabled.png"
+         ID    = "start_date_button_disabled"
+         STYLE = "display:none">
     <FONT SIZE=-1>(<% mt('leave blank to start immediately') |h %>)</FONT>
   </TD>
 </TR>
@@ -213,11 +216,6 @@ if ( $cgi->param('quantity') =~ /^\s*(\d+)\s*$/ ) {
 }
 
 my $format = $date_format. ' %T %z (%Z)'; #false laziness w/REAL_cust_pkg.cgi?
-my $start_date = '';
-if( ! $conf->exists('order_pkg-no_start_date') && $cust_main ) {
-  $start_date = $cust_main->next_bill_date;
-  $start_date = $start_date ? time2str($format, $start_date) : '';
-}
 
 my $svcpart = scalar($cgi->param('svcpart'));
 
index b48ff21..c9cfa40 100644 (file)
@@ -121,10 +121,7 @@ die "access denied"
   unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
 
 my ($agentnum,$sel_agent);
-if ( $cgi->param('agentnum') eq 'all' ) {
-  $agentnum = 0;
-}
-elsif ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
   $agentnum = $1;
   $sel_agent = qsearchs('agent', { 'agentnum' => $agentnum } );
   die "agentnum $agentnum not found!" unless $sel_agent;
@@ -177,10 +174,6 @@ my $query = FS::cust_main::Search->search(\%search_hash);
 my @custs = qsearch($query);
 
 foreach my $cust_main ( @custs ) {
-  # XXX should do this in the qsearch
-  next unless ($status eq '' || $status eq $cust_main->status); 
-  next unless ($agentnum == 0 || $cust_main->agentnum eq $agentnum);
-  next unless ($refnum   == 0 || $cust_main->refnum eq $refnum);
 
   push @custnames, $cust_main->name;