un-cancel, RT#17518
authorIvan Kohler <ivan@freeside.biz>
Thu, 3 May 2012 03:47:21 +0000 (20:47 -0700)
committerIvan Kohler <ivan@freeside.biz>
Thu, 3 May 2012 03:47:21 +0000 (20:47 -0700)
12 files changed:
FS/FS/AccessRight.pm
FS/FS/Schema.pm
FS/FS/access_right.pm
FS/FS/cust_pkg.pm
FS/FS/h_radius_usergroup.pm [new file with mode: 0644]
FS/FS/h_svc_Radius_Mixin.pm [new file with mode: 0644]
FS/FS/h_svc_acct.pm
FS/FS/h_svc_broadband.pm
httemplate/misc/cancel_pkg.html
httemplate/misc/process/cancel_pkg.html
httemplate/view/cust_main/packages/services.html
httemplate/view/cust_main/packages/status.html

index a11ad7f..914724c 100644 (file)
@@ -138,6 +138,7 @@ tie my %rights, 'Tie::IxHash',
     'Unsuspend customer package',
     'Cancel customer package immediately',
     'Cancel customer package later',
+    'Un-cancel customer package',
     'Delay suspension events',
     'Add on-the-fly cancel reason', #NEW
     'Add on-the-fly suspend reason', #NEW
index fb605ad..00c519e 100644 (file)
@@ -1512,6 +1512,8 @@ sub tables_hashref {
         'adjourn',        @date_type,             '', '', 
         'resume',         @date_type,             '', '', 
         'cancel',         @date_type,             '', '', 
+        'uncancel',       @date_type,             '', '', 
+        'uncancel_pkgnum',     'int', 'NULL', '', '', '',
         'expire',         @date_type,             '', '', 
         'contract_end',   @date_type,             '', '',
         'dundate',        @date_type,             '', '',
index fc01746..26a480b 100644 (file)
@@ -205,6 +205,7 @@ sub _upgrade_data { # class method
                             'Usage: Call Detail Records (CDRs)',
                             'Usage: Unrateable CDRs',
                           ],
+    'Cancel customer package immediately' => 'Un-cancel customer package',
   );
 
   foreach my $old_acl ( keys %onetime ) {
index 4359de9..5ccdb35 100644 (file)
@@ -12,7 +12,7 @@ use Time::Local qw( timelocal timelocal_nocheck );
 use MIME::Entity;
 use FS::UID qw( getotaker dbh );
 use FS::Misc qw( send_email );
-use FS::Record qw( qsearch qsearchs );
+use FS::Record qw( qsearch qsearchs fields );
 use FS::CurrentUser;
 use FS::cust_svc;
 use FS::part_pkg;
@@ -879,6 +879,143 @@ sub cancel_if_expired {
   '';
 }
 
+=item uncancel
+
+"Un-cancels" this package: Orders a new package with the same custnum, pkgpart,
+locationnum, (other fields?).  Attempts to re-provision cancelled services
+using history information (errors at this stage are not fatal).
+
+cust_pkg: pass a scalar reference, will be filled in with
+
+svc_errors: pass an array reference, will be filled in with any provisioning errors
+
+=cut
+
+sub uncancel {
+  my( $self, %options ) = @_;
+
+  #in case you try do do $uncancel-date = $cust_pkg->uncacel 
+  return '' unless $self->get('cancel');
+
+  ##
+  # Transaction-alize
+  ##
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE'; 
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE'; 
+  local $SIG{PIPE} = 'IGNORE'; 
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  ##
+  # insert the new package
+  ##
+
+  my $cust_pkg = new FS::cust_pkg {
+    last_bill       => ( $options{'last_bill'} || $self->get('last_bill') ),
+    bill            => ( $options{'bill'}      || $self->get('bill')      ),
+    uncancel        => time,
+    uncancel_pkgnum => $self->pkgnum,
+    map { $_ => $self->get($_) } qw(
+      custnum pkgpart locationnum
+      setup
+      susp adjourn resume expire start_date contract_end dundate
+      change_date change_pkgpart change_locationnum
+      manual_flag no_auto quantity agent_pkgid recur_show_zero setup_show_zero
+    ),
+  };
+
+  my $error = $cust_pkg->insert(
+    'change' => 1, #supresses any referral credit to a referring customer
+  );
+  if ($error) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  ##
+  # insert services
+  ##
+
+  #find historical services within this timeframe before the package cancel
+  # (incompatible with "time" option to cust_pkg->cancel?)
+  my $fuzz = 2 * 60; #2 minutes?  too much?   (might catch separate unprovision)
+                     #            too little? (unprovisioing export delay?)
+  my($end, $start) = ( $self->get('cancel'), $self->get('cancel') - $fuzz );
+  my @h_cust_svc = $self->h_cust_svc( $end, $start );
+
+  my @svc_errors;
+  foreach my $h_cust_svc (@h_cust_svc) {
+    my $h_svc_x = $h_cust_svc->h_svc_x( $end, $start );
+    #next unless $h_svc_x; #should this happen?
+    (my $table = $h_svc_x->table) =~ s/^h_//;
+    require "FS/$table.pm";
+    my $class = "FS::$table";
+    my $svc_x = $class->new( {
+      'pkgnum'  => $cust_pkg->pkgnum,
+      'svcpart' => $h_cust_svc->svcpart,
+      map { $_ => $h_svc_x->get($_) } fields($table)
+    } );
+
+    # radius_usergroup
+    if ( $h_svc_x->isa('FS::h_svc_Radius_Mixin') ) {
+      $svc_x->usergroup( [ $h_svc_x->h_usergroup($end, $start) ] );
+    }
+
+    my $svc_error = $svc_x->insert;
+    if ( $svc_error ) { #&& $options{svc_fatal} ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+    push @svc_errors, $svc_error if $svc_error;
+  }
+
+  #these are pretty rare, but should handle them
+  # - dsl_device (mac addresses)
+  # - phone_device (mac addresses)
+  # - dsl_note (ikano notes)
+  # - domain_record (i.e. restore DNS information w/domains)
+  # - inventory_item(?) (inventory w/un-cancelling service?)
+  # - nas (svc_broaband nas stuff)
+  #this stuff is unused in the wild afaik
+  # - mailinglistmember
+  # - router.svcnum?
+  # - svc_domain.parent_svcnum?
+  # - acct_snarf (ancient mail fetching config)
+  # - cgp_rule (communigate)
+  # - cust_svc_option (used by our Tron stuff)
+  # - acct_rt_transaction (used by our time worked stuff)
+
+  ##
+  # also move over any services that didn't unprovision at cancellation
+  ## 
+
+  foreach my $cust_svc ( qsearch('cust_svc', { pkgnum => $self->pkgnum } ) ) {
+    $cust_svc->pkgnum( $cust_pkg->pkgnum );
+    my $error = $cust_svc->replace;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  ##
+  # Finish
+  ##
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  ${ $options{cust_pkg} }   = $cust_pkg   if ref($options{cust_pkg});
+  @{ $options{svc_errors} } = @svc_errors if ref($options{svc_errors});
+
+  '';
+}
+
 =item unexpire
 
 Cancels any pending expiration (sets the expire field to null).
diff --git a/FS/FS/h_radius_usergroup.pm b/FS/FS/h_radius_usergroup.pm
new file mode 100644 (file)
index 0000000..bbccd6b
--- /dev/null
@@ -0,0 +1,24 @@
+package FS::h_radius_usergroup;
+
+use strict;
+use base qw( FS::h_Common FS::radius_usergroup );
+
+sub table { 'h_radius_usergroup' };
+
+=head1 NAME
+
+FS::h_radius_usergroup - Historical RADIUS usergroup records.
+
+=head1 DESCRIPTION
+
+An FS::h_radius_usergroup object represents historical changes to an account's
+RADIUS group (L<FS::radius_usergroup>).
+
+=head1 SEE ALSO
+
+L<FS::radius_usergroup>,  L<FS::h_Common>, L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/h_svc_Radius_Mixin.pm b/FS/FS/h_svc_Radius_Mixin.pm
new file mode 100644 (file)
index 0000000..af29770
--- /dev/null
@@ -0,0 +1,17 @@
+package FS::h_svc_Radius_Mixin;
+
+use strict;
+use FS::Record qw( qsearch );
+use FS::h_radius_usergroup;
+
+sub h_usergroup {
+  my $self = shift;
+  map { $_->groupnum } 
+    qsearch( 'h_radius_usergroup',
+             { svcnum => $self->svcnum },
+             FS::h_radius_usergroup->sql_h_searchs(@_),
+           );
+}
+
+1;
+
index 247d20c..f525f82 100644 (file)
@@ -1,16 +1,13 @@
 package FS::h_svc_acct;
+use base qw( FS::h_svc_Radius_Mixin FS::h_Common FS::svc_acct );
 
 use strict;
 use vars qw( @ISA $DEBUG );
 use Carp qw(carp);
 use FS::Record qw(qsearchs);
-use FS::h_Common;
-use FS::svc_acct;
 use FS::svc_domain;
 use FS::h_svc_domain;
 
-@ISA = qw( FS::h_Common FS::svc_acct );
-
 $DEBUG = 0;
 
 sub table { 'h_svc_acct' };
index d6038fb..01477fe 100644 (file)
@@ -1,11 +1,8 @@
 package FS::h_svc_broadband;
+use base qw( FS::h_svc_Radius_Mixin FS::h_Common FS::svc_broadband );
 
 use strict;
 use vars qw( @ISA );
-use FS::h_Common;
-use FS::svc_broadband;
-
-@ISA = qw( FS::h_Common FS::svc_broadband );
 
 sub table { 'h_svc_broadband' };
 
index 4b5df86..3a6a4d1 100755 (executable)
 % my $date_init = 0;
 % if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume') {
 %   $submit =~ /^(\w*)\s/;
-<& /elements/tr-input-date-field.html, {
-    'name'    => 'date',
-    'value'   => $date,
-    'label'   => mt("$1 package on"),
-    'format'  => $date_format,
-} &>
+  <& /elements/tr-input-date-field.html, {
+      'name'    => 'date',
+      'value'   => $date,
+      'label'   => mt("$1 package on"),
+      'format'  => $date_format,
+  } &>
 %   $date_init = 1;
 % }
 
-% unless ( $method eq 'resume' ) { #the only one that doesn't need a reason
-<& /elements/tr-select-reason.html,
-     'field'          => 'reasonnum',
-     'reason_class'   => $class,
-     'curr_value'     => $reasonnum,
-     'control_button' => "document.getElementById('confirm_cancel_pkg_button')",
-&>
+% if ($method eq 'uncancel' ) {
+%
+% #XXX customer also requested setup
+% # setup: what usefulness is changing or blanking this?  re-charge setup fee?
+% #        an option that says that would be better if that's what we want to do
+
+% # last_bill: isn't this informational?  what good would editing it do?
+% #            something about invoice display?
+  <& /elements/tr-input-date-field.html, {
+      'name'    => 'last_bill',
+      'value'   => ( $cgi->param('last_bill') || $cust_pkg->get('last_bill') ),
+      'label'   => mt("Last bill date"),
+      'format'  => $date_format,
+  } &>
+
+  <& /elements/tr-input-date-field.html, {
+      'name'    => 'bill',
+      'value'   => ( $cgi->param('bill') || $cust_pkg->get('bill') ),
+      'label'   => mt("Next bill date"),
+      'format'  => $date_format,
+  } &>
+
+%   $date_init = 1;
+% }
+
+% unless ( $method eq 'resume' || $method eq 'uncancel' ) {
+  <& /elements/tr-select-reason.html,
+       field          => 'reasonnum',
+       reason_class   => $class,
+       curr_value     => $reasonnum,
+       control_button => "document.getElementById('confirm_cancel_pkg_button')",
+  &>
 % }
 
 % if ( ( $method eq 'adjourn' or $method eq 'suspend' ) and 
 %                     ? str2time($cgi->param('resume_date'))
 %                     : $cust_pkg->get('resume');
 
-<& /elements/tr-input-date-field.html, {
-    'name'    => 'resume_date',
-    'value'   => $resume_date,
-    'label'   => mt('Unsuspend on'),
-    'format'  => $date_format,
-    'noinit'  => $date_init,
-} &>
+  <& /elements/tr-input-date-field.html, {
+      'name'    => 'resume_date',
+      'value'   => $resume_date,
+      'label'   => mt('Unsuspend on'),
+      'format'  => $date_format,
+      'noinit'  => $date_init,
+  } &>
 % }
 </TABLE>
 
 <BR>
 <INPUT TYPE="submit" NAME="submit" ID="confirm_cancel_pkg_button" 
   VALUE="<% mt($submit) |h %>"
-  <% $method ne 'resume' ? 'DISABLED' : '' %>>
+  <% $method !~ /^(resume|uncancel)$/ ? 'DISABLED' : '' %>>
 
 </FORM>
 </BODY>
 </HTML>
 
 <%init>
+use Date::Parse qw(str2time);
 
 my $conf = new FS::Conf;
 my $date_format = $conf->config('date_format') || '%m/%d/%Y';
@@ -99,6 +125,10 @@ if ($method eq 'cancel') {
   $class  = '';
   $submit = 'Unsuspend Later';
   $right  = 'Unsuspend customer package'; #later?
+} elsif ( $method eq 'uncancel') {
+  $class  = '';
+  $submit = 'Un-Cancel';
+  $right  = 'Un-cancel customer package'; #later?
 } else {
   die 'illegal query (unknown method param)';
 }
@@ -107,6 +137,7 @@ my $curuser = $FS::CurrentUser::CurrentUser;
 die "access denied" unless $curuser->access_right($right);
 
 my $title = ucfirst($method) . ' Package';
+$title =~ s/Uncancel/Un-cancel/;
 
 my $cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum})
   or die "Unknown pkgnum: $pkgnum";
index 662a776..bc3a8cd 100755 (executable)
@@ -6,19 +6,21 @@
 </HTML>
 <%once>
 
-my %past = ( 'cancel'  => 'cancelled',
-             'expire'  => 'expired',
-             'suspend' => 'suspended',
-             'adjourn' => 'adjourned',
-             'resume'  => 'scheduled to resume',
+my %past = ( 'cancel'   => 'cancelled',
+             'expire'   => 'expired',
+             'suspend'  => 'suspended',
+             'adjourn'  => 'adjourned',
+             'resume'   => 'scheduled to resume',
+             'uncancel' => 'un-cancelled',
            );
 
 #i'm sure this is false laziness with somewhere, at least w/misc/cancel_pkg.html
-my %right = ( 'cancel'  => 'Cancel customer package immediately',
-              'expire'  => 'Cancel customer package later',
-              'suspend' => 'Suspend customer package',
-              'adjourn' => 'Suspend customer package later',
-              'resume'  => 'Unsuspend customer package', #later?
+my %right = ( 'cancel'   => 'Cancel customer package immediately',
+              'expire'   => 'Cancel customer package later',
+              'suspend'  => 'Suspend customer package',
+              'adjourn'  => 'Suspend customer package later',
+              'resume'   => 'Unsuspend customer package', #later?
+              'uncancel' => 'Un-cancel customer package',
             );
 
 </%once>
@@ -26,7 +28,8 @@ my %right = ( 'cancel'  => 'Cancel customer package immediately',
 
 #untaint method
 my $method = $cgi->param('method');
-$method =~ /^(cancel|expire|suspend|adjourn|resume)$/ or die "Illegal method";
+$method =~ /^(cancel|expire|suspend|adjourn|resume|uncancel)$/
+  or die "Illegal method";
 $method = $1;
 my $past_method = $past{$method};
 
@@ -39,7 +42,7 @@ $pkgnum =~ /^(\d+)$/ or die "Illegal pkgnum";
 $pkgnum = $1;
 
 my $date = time;
-if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume'){
+if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume') {
   #untaint date
   $date = $cgi->param('date'); #huh?
   parse_datetime($cgi->param('date')) =~ /^(\d+)$/ or die "Illegal date";
@@ -59,7 +62,7 @@ my $cust_pkg = qsearchs( 'cust_pkg', {'pkgnum'=>$pkgnum} );
 
 #untaint reasonnum
 my $reasonnum = $cgi->param('reasonnum');
-if ( $method ne 'unsuspend' ) { #i.e. 'resume'
+if ( $method !~ /^(unsuspend|uncancel)$/ ) {
   $reasonnum =~ /^(-?\d+)$/ or die "Illegal reasonnum";
   $reasonnum = $1;
 
@@ -71,9 +74,18 @@ if ( $method ne 'unsuspend' ) { #i.e. 'resume'
   }
 }
 
+#for uncancel
+my $last_bill =
+  $cgi->param('last_bill') ? parse_datetime($cgi->param('last_bill')) : '';
+my $bill =
+  $cgi->param('bill')      ? parse_datetime($cgi->param('bill'))      : '';
+
 my $error = $cust_pkg->$method( 'reason'      => $reasonnum,
                                 'date'        => $date,
-                                'resume_date' => $resume_date );
+                                'resume_date' => $resume_date,
+                                'last_bill'   => $last_bill,
+                                'bill'        => $bill,
+                              );
 
 if ($error) {
   $cgi->param('error', $error);
index 5f458e6..c0a56d0 100644 (file)
@@ -3,7 +3,7 @@
 % ###
 
   <TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
-    <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%">
+    <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=2 WIDTH="100%">
     <SCRIPT TYPE="text/javascript">
 function clearhint_search_cust_svc(obj, str) {
   if (obj.value == str) obj.value = '';
index 28df9da..4aec90e 100644 (file)
 
 %   } 
 %
-% } else { 
+%   if ( $part_pkg->freq ) { #?
+
+      <TR>
+        <TD COLSPAN=<%$colspan%>>
+          <FONT SIZE=-1>
+%           if ( $curuser->access_right('Un-cancel customer package') ) { 
+              (&nbsp;<% pkg_uncancel_link($cust_pkg) %>&nbsp;)
+%           } 
+          <FONT>
+        </TD>
+      </TR>
+%   }
+%
+% } else {
 %
 %   if ( $cust_pkg->get('susp') ) { #status: suspended
 %     my $cpr = $cust_pkg->last_cust_pkg_reason('susp');
@@ -56,6 +69,8 @@
       <% pkg_status_row($cust_pkg, emt('Setup'), 'setup', %opt ) %>
 %   } 
 
+    <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
+
     <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
     <% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %>
 %   if ( $part_pkg->option('suspend_bill', 1) ) {
              )
           %>
 
+          <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
+
           <TR>
             <TD COLSPAN=<%$colspan%>>
               <FONT SIZE=-1>
           <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
 
           <% pkg_status_row_if($cust_pkg, emt('Start billing'), 'start_date', %opt) %>
+          <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
 
 %       } 
 %
 
           <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
 
+          <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
+
 %       } else { 
 %
 %         my $num_cust_svc = $cust_pkg->num_cust_svc;
 
           <% pkg_status_row($cust_pkg, emt('Setup'), 'setup', %opt) %>
 
+          <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
+
 %       } 
 %
 %     } 
@@ -467,6 +489,16 @@ sub pkg_cancel_link {
          )
 }
 
+sub pkg_uncancel_link {
+  include( '/elements/popup_link-cust_pkg.html',
+             'action'      => $p. 'misc/cancel_pkg.html?method=uncancel',
+             'label'       => emt('Un-cancel'),
+             'actionlabel' => emt('Un-cancel'),
+             #'color'       =>  #?
+             'cust_pkg'    => shift,
+         )
+}
+
 sub pkg_expire_link {
   include( '/elements/popup_link-cust_pkg.html',
              'action'      => $p. 'misc/cancel_pkg.html?method=expire',