From: Ivan Kohler Date: Thu, 3 May 2012 03:47:21 +0000 (-0700) Subject: un-cancel, RT#17518 X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=6aa1a0eeb1c28caf6af94a1323f69f3bb4256302 un-cancel, RT#17518 --- diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index a11ad7f1b..914724cc3 100644 --- a/FS/FS/AccessRight.pm +++ b/FS/FS/AccessRight.pm @@ -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 diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index fb605ad78..00c519e5b 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -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, '', '', diff --git a/FS/FS/access_right.pm b/FS/FS/access_right.pm index fc0174602..26a480b2f 100644 --- a/FS/FS/access_right.pm +++ b/FS/FS/access_right.pm @@ -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 ) { diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 4359de9a4..5ccdb354c 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -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 index 000000000..bbccd6bb7 --- /dev/null +++ b/FS/FS/h_radius_usergroup.pm @@ -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). + +=head1 SEE ALSO + +L, L, L + +=cut + +1; + diff --git a/FS/FS/h_svc_Radius_Mixin.pm b/FS/FS/h_svc_Radius_Mixin.pm new file mode 100644 index 000000000..af2977085 --- /dev/null +++ b/FS/FS/h_svc_Radius_Mixin.pm @@ -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; + diff --git a/FS/FS/h_svc_acct.pm b/FS/FS/h_svc_acct.pm index 247d20c9a..f525f8206 100644 --- a/FS/FS/h_svc_acct.pm +++ b/FS/FS/h_svc_acct.pm @@ -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' }; diff --git a/FS/FS/h_svc_broadband.pm b/FS/FS/h_svc_broadband.pm index d6038fbe8..01477fe1c 100644 --- a/FS/FS/h_svc_broadband.pm +++ b/FS/FS/h_svc_broadband.pm @@ -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' }; diff --git a/httemplate/misc/cancel_pkg.html b/httemplate/misc/cancel_pkg.html index 4b5df8654..3a6a4d14c 100755 --- a/httemplate/misc/cancel_pkg.html +++ b/httemplate/misc/cancel_pkg.html @@ -13,22 +13,47 @@ % 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 @@ -37,26 +62,27 @@ % ? 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, + } &> % }
> + <% $method !~ /^(resume|uncancel)$/ ? 'DISABLED' : '' %>> <%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"; diff --git a/httemplate/misc/process/cancel_pkg.html b/httemplate/misc/process/cancel_pkg.html index 662a77670..bc3a8cd3f 100755 --- a/httemplate/misc/process/cancel_pkg.html +++ b/httemplate/misc/process/cancel_pkg.html @@ -6,19 +6,21 @@ <%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', ); @@ -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); diff --git a/httemplate/view/cust_main/packages/services.html b/httemplate/view/cust_main/packages/services.html index 5f458e635..c0a56d0f3 100644 --- a/httemplate/view/cust_main/packages/services.html +++ b/httemplate/view/cust_main/packages/services.html @@ -3,7 +3,7 @@ % ### - +