Merge branch 'master' of git.freeside.biz:/home/git/freeside
authorIvan Kohler <ivan@freeside.biz>
Wed, 11 Feb 2015 19:56:39 +0000 (11:56 -0800)
committerIvan Kohler <ivan@freeside.biz>
Wed, 11 Feb 2015 19:56:39 +0000 (11:56 -0800)
12 files changed:
FS/FS/API.pm
FS/FS/Conf.pm
FS/FS/cdr/cia.pm
FS/FS/cdr/cia_callblast.pm [new file with mode: 0644]
FS/FS/cust_main.pm
FS/FS/cust_pkg.pm
FS/FS/part_pkg/global_Mixin.pm
FS/FS/reason.pm
bin/xmlrpc-customer_bill_now [new file with mode: 0755]
httemplate/elements/change_history_common.html
httemplate/misc/cust_main-cancel.cgi
httemplate/view/cust_main/misc.html

index dd172c1..c49fb20 100644 (file)
@@ -603,8 +603,21 @@ sub location_info {
 Bills a single customer now, in the same fashion as the "Bill now" link in the
 UI.
 
-Returns a hash reference with a single key, 'error'.  If there is an error,
-the value contains the error, otherwise it is empty.
+Returns a hash reference with a single key, 'error'.  If there is an error,   
+the value contains the error, otherwise it is empty. Takes a list of keys and
+values as parameters with the following keys:
+
+=over 4
+
+=item secret
+
+API Secret (required)
+
+=item custnum
+
+Customer number (required)
+
+=back
 
 =cut
 
index a4e26f7..091070e 100644 (file)
@@ -4627,6 +4627,16 @@ and customer address. Include units.',
   },
 
   {
+    'key'         => 'part_pkg-delay_cancel-days',
+    'section'     => '',
+    'description' => 'Expire packages in this many days when using delay_cancel (default is 1)',
+    'type'        => 'text',
+    'validate'    => sub { (($_[0] =~ /^\d*$/) && (($_[0] eq '') || $_[0]))
+                           ? 'Must specify an integer number of days'
+                           : '' }
+  },
+
+  {
     'key'         => 'mcp_svcpart',
     'section'     => '',
     'description' => 'Master Control Program svcpart.  Leave this blank.',
index 0471e9b..ca44c0f 100644 (file)
@@ -1,9 +1,8 @@
 package FS::cdr::cia;
 
 use strict;
-use vars qw( @ISA %info $date $tmp_mday $tmp_mon $tmp_year);
+use vars qw( @ISA %info );
 use FS::cdr qw(_cdr_date_parser_maker);
-use Time::Local;
 
 @ISA = qw(FS::cdr);
 
@@ -12,39 +11,26 @@ use Time::Local;
   'weight'        => 510,
   'header'        => 1,
   'type'          => 'csv',
-  'sep_char'      => "|",
+  'sep_char'      => "\t",
   'import_fields' => [
-    'accountcode',
-    skip(2),          # First and last name
-
-    sub { my($cdr, $date) = @_;
-          $date =~ /^(\d{1,2})\/(\d{1,2})\/(\d\d(\d\d)?)$/
-            or die "unparsable date: $date"; #maybe we shouldn't die...
-          ($tmp_mday, $tmp_mon, $tmp_year) = ( $2, $1-1, $3 );
-        }, #Date
-
-    sub { my($cdr, $time) = @_;
-          #my($sec, $min, $hour, $mday, $mon, $year)= localtime($cdr->startdate);
-          $time =~ /^(\d{1,2}):(\d{1,2}):(\d{1,2})$/
-            or die "unparsable time: $time"; #maybe we shouldn't die...
-          $cdr->startdate( timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year));
-          $cdr->answerdate( timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year));
-         
-        }, # Start time
-
-    sub { my($cdr, $time) = @_;
-          #my($sec, $min, $hour, $mday, $mon, $year)= localtime($cdr->startdate);
-          $time =~ /^(\d{1,2}):(\d{1,2}):(\d{1,2})$/
-            or die "unparsable time: $time"; #maybe we shouldn't die...
-          #$cdr->startdate( timelocal($3, $2, $1 ,$mday, $mon, $year) );
-          $cdr->enddate(
-            timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year) ); 
-        }, # End time
-
-    'disposition',    # Disposition
-    'dst',            # PhoneNumber
-    skip(3),          # Extension, Service Type, Filler
-    'src',            # ClientContactID
+    skip(2),          # Reseller Account Number, Confirmation Number
+    'description',    # Conference Name
+    skip(3),          # Organization Name, Bill Code, Q&A Active 
+    'userfield',      # Chairperson Name
+    skip(2),          # Conference Start Time, Conference End Time
+    _cdr_date_parser_maker('startdate'),  # Connect Time
+    _cdr_date_parser_maker('enddate'),    # Disconnect Time
+    skip(1),          # Duration
+    sub { my($cdr, $data, $conf, $param) = @_;
+          $cdr->duration($data);
+          $cdr->billsec( $data);
+    },                # Roundup Duration
+    skip(1),          # User Name
+    'dst',            # DNIS
+    'src',            # ANI
+    skip(2),          # Call Type, Toll Free, 
+    'accountcode',    # Chair Conference Entry Code
+    skip(1),          # Participant Conference Entry Code,
     ],
 
 );
diff --git a/FS/FS/cdr/cia_callblast.pm b/FS/FS/cdr/cia_callblast.pm
new file mode 100644 (file)
index 0000000..d59cd78
--- /dev/null
@@ -0,0 +1,54 @@
+package FS::cdr::cia_callblast;
+
+use strict;
+use vars qw( @ISA %info $date $tmp_mday $tmp_mon $tmp_year);
+use FS::cdr qw(_cdr_date_parser_maker);
+use Time::Local;
+
+@ISA = qw(FS::cdr);
+
+%info = (
+  'name'          => 'Client Instant Access Callblast',
+  'weight'        => 510,
+  'header'        => 1,
+  'type'          => 'csv',
+  'sep_char'      => "|",
+  'import_fields' => [
+    'accountcode',
+    skip(2),          # First and last name
+
+    sub { my($cdr, $date) = @_;
+          $date =~ /^(\d{1,2})\/(\d{1,2})\/(\d\d(\d\d)?)$/
+            or die "unparsable date: $date"; #maybe we shouldn't die...
+          ($tmp_mday, $tmp_mon, $tmp_year) = ( $2, $1-1, $3 );
+        }, #Date
+
+    sub { my($cdr, $time) = @_;
+          #my($sec, $min, $hour, $mday, $mon, $year)= localtime($cdr->startdate);
+          $time =~ /^(\d{1,2}):(\d{1,2}):(\d{1,2})$/
+            or die "unparsable time: $time"; #maybe we shouldn't die...
+          $cdr->startdate( timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year));
+          $cdr->answerdate( timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year));
+         
+        }, # Start time
+
+    sub { my($cdr, $time) = @_;
+          #my($sec, $min, $hour, $mday, $mon, $year)= localtime($cdr->startdate);
+          $time =~ /^(\d{1,2}):(\d{1,2}):(\d{1,2})$/
+            or die "unparsable time: $time"; #maybe we shouldn't die...
+          #$cdr->startdate( timelocal($3, $2, $1 ,$mday, $mon, $year) );
+          $cdr->enddate(
+            timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year) ); 
+        }, # End time
+
+    'disposition',    # Disposition
+    'dst',            # PhoneNumber
+    skip(3),          # Extension, Service Type, Filler
+    'src',            # ClientContactID
+    ],
+
+);
+
+sub skip { map {''} (1..$_[0]) }
+
+1;
index d38f3d0..c3b141e 100644 (file)
@@ -3900,6 +3900,27 @@ sub cust_status {
   }
 }
 
+=item is_status_delay_cancel
+
+Returns true if customer status is 'suspended'
+and all suspended cust_pkg return true for
+cust_pkg->is_status_delay_cancel.
+
+This is not a real status, this only meant for hacking display 
+values, because otherwise treating the customer as suspended is 
+really the whole point of the delay_cancel option.
+
+=cut
+
+sub is_status_delay_cancel {
+  my ($self) = @_;
+  return 0 unless $self->status eq 'suspended';
+  foreach my $cust_pkg ($self->ncancelled_pkgs) {
+    return 0 unless $cust_pkg->is_status_delay_cancel;
+  }
+  return 1;
+}
+
 =item ucfirst_cust_status
 
 =item ucfirst_status
index 3bd2107..1cc83b6 100644 (file)
@@ -823,6 +823,20 @@ sub cancel {
   my $date = $options{'date'} if $options{'date'}; # expire/cancel later
   $date = '' if ($date && $date <= $cancel_time);      # complain instead?
 
+  my $delay_cancel = undef;
+  if ( !$date && $self->part_pkg->option('delay_cancel',1)
+       && (($self->status eq 'active') || ($self->status eq 'suspended'))
+  ) {
+    my $expdays = $conf->config('part_pkg-delay_cancel-days') || 1;
+    my $expsecs = 60*60*24*$expdays;
+    my $suspfor = $self->susp ? $cancel_time - $self->susp : 0;
+    $expsecs = $expsecs - $suspfor if $suspfor;
+    unless ($expsecs <= 0) { #if it's already been suspended long enough, don't re-suspend
+      $delay_cancel = 1;
+      $date = $cancel_time + $expsecs;
+    }
+  }
+
   #race condition: usage could be ongoing until unprovisioned
   #resolved by performing a change package instead (which unprovisions) and
   #later cancelling
@@ -893,6 +907,11 @@ sub cancel {
   my %hash = $self->hash;
   if ( $date ) {
     $hash{'expire'} = $date;
+    if ($delay_cancel) {
+      $hash{'susp'} = $cancel_time unless $self->susp;
+      $hash{'adjourn'} = undef;
+      $hash{'resume'} = undef;
+    }
   } else {
     $hash{'cancel'} = $cancel_time;
   }
@@ -1643,7 +1662,7 @@ sub unsuspend {
          )
       or $hash{'order_date'} == $hash{'susp'}
       or $self->part_pkg->option('unused_credit_suspend')
-      or ( defined($reason) and $reason->unused_credit )
+      or ( ref($reason) and $reason->unused_credit )
   ) {
     $adjust_bill = 0;
   }
@@ -3343,6 +3362,31 @@ sub statuscolor {
   $statuscolor{$self->status};
 }
 
+=item is_status_delay_cancel
+
+Returns true if part_pkg has option delay_cancel, 
+cust_pkg status is 'suspended' and expire is set
+to cancel package within the next day (or however
+many days are set in global config part_pkg-delay_cancel-days.
+
+This is not a real status, this only meant for hacking display 
+values, because otherwise treating the package as suspended is 
+really the whole point of the delay_cancel option.
+
+=cut
+
+sub is_status_delay_cancel {
+  my ($self) = @_;
+  return 0 unless $self->part_pkg->option('delay_cancel',1);
+  return 0 unless $self->status eq 'suspended';
+  return 0 unless $self->expire;
+  my $conf = new FS::Conf;
+  my $expdays = $conf->config('part_pkg-delay_cancel-days') || 1;
+  my $expsecs = 60*60*24*$expdays;
+  return 0 unless $self->expire < time + $expsecs;
+  return 1;
+}
+
 =item pkg_label
 
 Returns a label for this package.  (Currently "pkgnum: pkg - comment" or
index 2637729..2318c3e 100644 (file)
@@ -40,6 +40,10 @@ tie my %a2billing_simultaccess, 'Tie::IxHash', (
                 'changing packages',
       'type' => 'checkbox',
     },
+    'delay_cancel' => {
+      'name' => 'Automatically suspend for one day before cancelling',
+      'type' => 'checkbox',
+    },
 
     # miscellany--maybe put this in a separate module?
 
@@ -109,6 +113,7 @@ tie my %a2billing_simultaccess, 'Tie::IxHash', (
     unused_credit_cancel
     unused_credit_suspend
     unused_credit_change
+    delay_cancel
 
     a2billing_tariff
     a2billing_type
index 864804d..9c34dd9 100644 (file)
@@ -174,7 +174,7 @@ sub new_or_existing {
     }
   } else {
     my %hash = ('class' => $opt{'class'}, 'type' => $opt{'type'});
-    my $reason_type = qsearchs('reason_type', \%hash)
+    $reason_type = qsearchs('reason_type', \%hash)
                       || FS::reason_type->new(\%hash);
 
     $error = $reason_type->insert unless $reason_type->typenum;
diff --git a/bin/xmlrpc-customer_bill_now b/bin/xmlrpc-customer_bill_now
new file mode 100755 (executable)
index 0000000..244b66f
--- /dev/null
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+
+use strict;
+use Frontier::Client;
+use Data::Dumper;
+
+my $uri = new URI 'http://localhost:8008/';
+
+my $server = new Frontier::Client ( 'url' => $uri );
+
+my $result = $server->call(
+  'FS.API.bill_now',
+    'secret'  => 'sharingiscaring',
+    'custnum' => 3,
+);
+
+#die $result->{'error'} if $result->{'error'};
+
+print Dumper($result);
+
+1;
index 9e32bef..9fc85aa 100644 (file)
                 $item->fields
          )
       %>
+% if ( $single_cust && $h_table_descripsub{$item->table} ) {
+      <% &{ $h_table_descripsub{$item->table} }( $item ) %>
+% }
     </TD>
   </TR>
 
@@ -172,18 +175,6 @@ my $svc_labelsub = sub {
   $label. ': <b>'. encode_entities($item->label($item->history_date)). '</b>';
 };
 
-my $discounts = {};
-my $discount_labelsub = sub {
-  my($item, $label) = @_;
-  my $dnum = $item->discountnum;
-  $discounts->{$dnum} ||= qsearchs({
-    'table'=>'discount',
-    'hashref'=>{'discountnum'=>$dnum}
-  });
-  my $d = $discounts->{$dnum};
-  $label . ': <b>' . encode_entities($d->description_short) . '<b>';
-};
-
 my %h_table_labelsub = (
   'h_cust_pkg'      => $pkg_labelsub,
   'h_svc_acct'      => $svc_labelsub,
@@ -195,7 +186,24 @@ my %h_table_labelsub = (
   'h_svc_external'  => $svc_labelsub,
   'h_svc_phone'     => $svc_labelsub,
   #'h_phone_device'
-  'h_cust_pkg_discount' => $discount_labelsub,
+);
+
+my $discounts = {};
+my $discount_descripsub = sub {
+  my($item) = @_;
+  $pkgpart{$item->pkgpart} ||= $item->part_pkg->pkg;
+  my $dnum = $item->discountnum;
+  $discounts->{$dnum} ||= qsearchs({
+    'table'=>'discount',
+    'hashref'=>{'discountnum'=>$dnum}
+  });
+  my $d = $discounts->{$dnum};
+  '(<b>' . encode_entities($d->description_short) . '</b>'
+  . ' on <b>' . encode_entities($pkgpart{$item->pkgpart}) . '</b>)';
+};
+
+my %h_table_descripsub = (
+  'h_cust_pkg_discount' => $discount_descripsub,
 );
 
 my $cust_pkg_date_format = '%b %o, %Y';
index a78a8b3..f6fd1e9 100755 (executable)
@@ -54,10 +54,11 @@ if ( $error ) {
 }
 else {
   warn "cancelling $cust_main";
-  $error = $cust_main->cancel(
+  my @error = $cust_main->cancel( #returns list of errors
     'ban'    => $ban,
     'reason' => $reasonnum,
   );
+  $error = join(', ',@error);
 }
 
 if ( $error ) {
index 15def32..fe0e329 100644 (file)
@@ -7,7 +7,7 @@
 
 <TR>
   <TD ALIGN="right"><% mt('Status') |h %></TD>
-  <TD BGCOLOR="#ffffff"><FONT COLOR="#<% $cust_main->statuscolor %>"><B><% $cust_main->status_label %></B></FONT></TD>
+  <TD BGCOLOR="#ffffff"><FONT COLOR="#<% $cust_main->statuscolor %>"><B><% $status_label %></B></FONT></TD>
 </TR>
 
 % my @part_tag = $cust_main->part_tag;
@@ -204,4 +204,9 @@ my $curuser = $FS::CurrentUser::CurrentUser;
 
 my @agentnums = $curuser->agentnums;
 
+my $status_label = $cust_main->status_label;
+if ($cust_main->is_status_delay_cancel) {
+  $status_label .= ' (Cancelled)';
+}
+
 </%init>