From 781f0ffcf560d3df0aec7ae349b57463d1c2518a Mon Sep 17 00:00:00 2001 From: mark Date: Sat, 28 Jan 2012 23:20:11 +0000 Subject: [PATCH] future package unsuspend date, #14144 --- FS/FS/Cron/bill.pm | 3 +- FS/FS/Schema.pm | 1 + FS/FS/cust_main/Billing.pm | 31 +++++++++- FS/FS/cust_pkg.pm | 68 +++++++++++++++++++--- httemplate/edit/REAL_cust_pkg.cgi | 3 +- httemplate/edit/process/REAL_cust_pkg.cgi | 3 +- httemplate/elements/change_history_common.html | 2 +- httemplate/misc/cancel_pkg.html | 78 +++++++++++++++----------- httemplate/misc/process/cancel_pkg.html | 47 ++++++++++------ httemplate/view/cust_main/packages/status.html | 17 +++++- 10 files changed, 188 insertions(+), 65 deletions(-) diff --git a/FS/FS/Cron/bill.pm b/FS/FS/Cron/bill.pm index 64979baec..8d1223b80 100644 --- a/FS/FS/Cron/bill.pm +++ b/FS/FS/Cron/bill.pm @@ -146,7 +146,7 @@ sub bill { # (or now, if no -d switch was given). # # -n: When used with "-d" and/or "-y", specifies that invoices should be dated -# with today's date, irregardless of the pretend date used to pre-generate +# with today's date, regardless of the pretend date used to pre-generate # the invoices. # # -p: Only process customers with the specified payby (I, I, I, I, I, I, I) @@ -211,6 +211,7 @@ sub bill_where { OR bill IS NULL OR bill <= $billtime OR ( expire IS NOT NULL AND expire <= $^T ) OR ( adjourn IS NOT NULL AND adjourn <= $^T ) + OR ( resume IS NOT NULL AND resume <= $^T ) ) ) END diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 6727420ca..9de1b7f3f 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -1481,6 +1481,7 @@ sub tables_hashref { 'last_bill', @date_type, '', '', 'susp', @date_type, '', '', 'adjourn', @date_type, '', '', + 'resume', @date_type, '', '', 'cancel', @date_type, '', '', 'expire', @date_type, '', '', 'contract_end', @date_type, '', '', diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm index 23d3b4933..ed7b0fa77 100644 --- a/FS/FS/cust_main/Billing.pm +++ b/FS/FS/cust_main/Billing.pm @@ -129,6 +129,14 @@ sub bill_and_collect { else { warn $error; } } + $error = $self->unsuspend_resumed_pkgs( day_end( $options{actual_time} ) ); + if ( $error ) { + $error = "Error resuming custnum ".$self->custnum. ": $error"; + if ( $options{fatal} && $options{fatal} eq 'return' ) { return $error; } + elsif ( $options{fatal} ) { die $error; } + else { warn $error; } + } + $job->update_statustext('20,billing packages') if $job; $error = $self->bill( %options ); if ( $error ) { @@ -185,7 +193,7 @@ sub cancel_expired_pkgs { push @errors, 'pkgnum '.$cust_pkg->pkgnum.": $error" if $error; } - scalar(@errors) ? join(' / ', @errors) : ''; + join(' / ', @errors); } @@ -227,7 +235,25 @@ sub suspend_adjourned_pkgs { push @errors, 'pkgnum '.$cust_pkg->pkgnum.": $error" if $error; } - scalar(@errors) ? join(' / ', @errors) : ''; + join(' / ', @errors); + +} + +sub unsuspend_resumed_pkgs { + my ( $self, $time, %options ) = @_; + + my @unsusp_pkgs = $self->ncancelled_pkgs( { + 'extra_sql' => " AND resume IS NOT NULL AND resume > 0 AND resume <= $time " + } ); + + my @errors = (); + + foreach my $cust_pkg ( @unsusp_pkgs ) { + my $error = $cust_pkg->unsuspend( 'time' => $time ); + push @errors, 'pkgnum '.$cust_pkg->pkgnum.": $error" if $error; + } + + join(' / ', @errors); } @@ -2168,6 +2194,7 @@ sub apply_payments { cancel_expired_pkgs suspend_adjourned_pkgs + unsuspend_resumed_pkgs bill (do_cust_event pre-bill) diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 469bd94ab..855accc0c 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -605,6 +605,7 @@ sub check { || $self->ut_numbern('susp') || $self->ut_numbern('cancel') || $self->ut_numbern('adjourn') + || $self->ut_numbern('resume') || $self->ut_numbern('expire') || $self->ut_numbern('dundate') || $self->ut_enum('no_auto', [ '', 'Y' ]) @@ -618,6 +619,9 @@ sub check { return "A package with both start date (future start) and setup date (already started) will never bill" if $self->start_date && $self->setup; + return "A future unsuspend date can only be set for a package with a suspend date" + if $self->resume and !$self->susp and !$self->adjourn; + $self->usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->usernum; if ( $self->dbdef_table->column('manual_flag') ) { @@ -936,9 +940,21 @@ Available options are: =over 4 -=item reason - can be set to a cancellation reason (see L), either a reasonnum of an existing reason, or passing a hashref will create a new reason. The hashref should have the following keys: typenum - Reason type (see L, reason - Text of the new reason. +=item reason - can be set to a cancellation reason (see L), +either a reasonnum of an existing reason, or passing a hashref will create +a new reason. The hashref should have the following keys: +- typenum - Reason type (see L +- reason - Text of the new reason. + +=item date - can be set to a unix style timestamp to specify when to +suspend (adjourn) + +=item time - can be set to override the current time, for calculation +of final invoices or unused-time credits -=item date - can be set to a unix style timestamp to specify when to suspend (adjourn) +=item resume_date - can be set to a time when the package should be +unsuspended. This may be more convenient than calling C +separately. =back @@ -976,7 +992,7 @@ sub suspend { my $suspend_time = $options{'time'} || time; my $date = $options{date} if $options{date}; # adjourn/suspend later - $date = '' if ($date && $date <= time); # complain instead? + $date = '' if ($date && $date <= $suspend_time); # complain instead? if ( $date && $old->get('expire') && $old->get('expire') < $date ) { dbh->rollback if $oldAutoCommit; @@ -1019,6 +1035,12 @@ sub suspend { } else { $hash{'susp'} = $suspend_time; } + + my $resume_date = $options{'resume_date'} || 0; + if ( $resume_date > ($date || $suspend_time) ) { + $hash{'resume'} = $resume_date; + } + my $new = new FS::cust_pkg ( \%hash ); $error = $new->replace( $self, options => { $self->options } ); if ( $error ) { @@ -1098,7 +1120,7 @@ sub suspend { Generate a credit for this package for the time remaining in the current billing period. MODE is either "suspend" or "cancel" (determines the -credit type). TIME is the time of suspension/cancellation. Both options +credit type). TIME is the time of suspension/cancellation. Both arguments are mandatory. =cut @@ -1146,6 +1168,11 @@ Available options are: =over 4 +=item date + +Can be set to a date to unsuspend the package in the future (the 'resume' +field). + =item adjust_next_bill Can be set true to adjust the next bill date forward by @@ -1180,15 +1207,38 @@ sub unsuspend { my $pkgnum = $old->pkgnum; if ( $old->get('cancel') || $self->get('cancel') ) { - dbh->rollback if $oldAutoCommit; + $dbh->rollback if $oldAutoCommit; return "Can't unsuspend cancelled package $pkgnum"; } unless ( $old->get('susp') && $self->get('susp') ) { - dbh->rollback if $oldAutoCommit; + $dbh->rollback if $oldAutoCommit; return ""; # no error # complain instead? } + my $date = $opt{'date'}; + if ( $date and $date > time ) { # return an error if $date <= time? + + if ( $old->get('expire') && $old->get('expire') < $date ) { + $dbh->rollback if $oldAutoCommit; + return "Package $pkgnum expires before it would be unsuspended."; + } + + my $new = new FS::cust_pkg { $self->hash }; + $new->set('resume', $date); + $error = $new->replace($self, options => $self->options); + + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + else { + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + return ''; + } + + } #if $date + foreach my $cust_svc ( qsearch('cust_svc',{'pkgnum'=> $self->pkgnum } ) ) { @@ -1229,7 +1279,8 @@ sub unsuspend { } $hash{'susp'} = ''; - $hash{'adjourn'} = '' if $hash{'adjourn'} < time; + $hash{'adjourn'} = '' if $hash{'adjourn'} and $hash{'adjourn'} < time; + $hash{'resume'} = '' if !$hash{'adjourn'}; my $new = new FS::cust_pkg ( \%hash ); $error = $new->replace( $self, options => { $self->options } ); if ( $error ) { @@ -1287,6 +1338,7 @@ sub unadjourn { my %hash = $self->hash; $hash{'adjourn'} = ''; + $hash{'resume'} = ''; my $new = new FS::cust_pkg ( \%hash ); $error = $new->replace( $self, options => { $self->options } ); if ( $error ) { @@ -1409,7 +1461,7 @@ sub change { if ( $keep_dates ) { foreach my $date ( qw(setup bill last_bill susp adjourn cancel expire - start_date contract_end ) ) { + resume start_date contract_end ) ) { $hash{$date} = $self->getfield($date); } } diff --git a/httemplate/edit/REAL_cust_pkg.cgi b/httemplate/edit/REAL_cust_pkg.cgi index 170281ba0..0e18a52b2 100755 --- a/httemplate/edit/REAL_cust_pkg.cgi +++ b/httemplate/edit/REAL_cust_pkg.cgi @@ -61,6 +61,7 @@ %#} <& .row_display, cust_pkg=>$cust_pkg, column=>'adjourn', label=>'Adjournment', note=>'(will suspend this package when the date is reached)' &> <& .row_display, cust_pkg=>$cust_pkg, column=>'susp', label=>'Suspension' &> + <& .row_display, cust_pkg=>$cust_pkg, column=>'resume', label=>'Resumption', note=> '(will unsuspend this package when the date is reached' &> <& .row_display, cust_pkg=>$cust_pkg, column=>'expire', label=>'Expiration', note=>'(will cancel this package when the date is reached)' &> <& .row_display, cust_pkg=>$cust_pkg, column=>'cancel', label=>'Cancellation' &> @@ -192,7 +193,7 @@ if ( $cgi->param('error') ) { $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); die "No package!" unless $cust_pkg; - foreach my $col (qw( start_date setup last_bill bill adjourn expire )) { + foreach my $col (qw( start_date setup last_bill bill )) { my $value = $cgi->param($col); $cust_pkg->set( $col, $value ? parse_datetime($value) : '' ); } diff --git a/httemplate/edit/process/REAL_cust_pkg.cgi b/httemplate/edit/process/REAL_cust_pkg.cgi index 3a62ee001..9c36c8b77 100755 --- a/httemplate/edit/process/REAL_cust_pkg.cgi +++ b/httemplate/edit/process/REAL_cust_pkg.cgi @@ -20,7 +20,8 @@ my $pkgnum = $cgi->param('pkgnum') or die; my $old = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); my %hash = $old->hash; $hash{$_}= $cgi->param($_) ? parse_datetime($cgi->param($_)) : '' - foreach qw( start_date setup bill last_bill adjourn expire contract_end ); + foreach qw( start_date setup bill last_bill contract_end ); + # adjourn, expire, resume not editable this way my @errors = (); diff --git a/httemplate/elements/change_history_common.html b/httemplate/elements/change_history_common.html index 9e19539d0..232664e39 100644 --- a/httemplate/elements/change_history_common.html +++ b/httemplate/elements/change_history_common.html @@ -138,7 +138,7 @@ my %action = ( my %cust_pkg_date_fields = map { $_=>1 } qw( start_date setup bill last_bill susp adjourn cancel expire contract_end - change_date + resume change_date ); # finding the other replace row diff --git a/httemplate/misc/cancel_pkg.html b/httemplate/misc/cancel_pkg.html index 6e02e0e39..4b5df8654 100755 --- a/httemplate/misc/cancel_pkg.html +++ b/httemplate/misc/cancel_pkg.html @@ -1,10 +1,5 @@ <& /elements/header-popup.html, mt($title) &> - - - - - <& /elements/error.html &>
@@ -15,37 +10,47 @@ <% emt(ucfirst($method)." [_1]", $part_pkg->pkg_comment) %> <% ntable("#cccccc", 2) %> -% if ($method eq 'expire' || $method eq 'adjourn') { - -% $submit =~ /^(\w*)\s/; - <% mt("$1 package on") |h %> - - -
<% mt('m/d/y') |h %> - - - -%} -% - +% 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, +} &> +% $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')", + '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 +% $curuser->access_right('Unsuspend customer package') ) { #later? +% my $resume_date = $cgi->param('error') +% ? 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, +} &> +% }
- +>
@@ -56,13 +61,13 @@ my $conf = new FS::Conf; my $date_format = $conf->config('date_format') || '%m/%d/%Y'; -my $date = time2str($date_format, time); +my $date; my($pkgnum, $reasonnum); if ( $cgi->param('error') ) { $pkgnum = $cgi->param('pkgnum'); $reasonnum = $cgi->param('reasonnum'); - $date = $cgi->param('date'); + $date = str2time($cgi->param('date')); } elsif ( $cgi->param('pkgnum') =~ /^(\d+)$/ ) { $pkgnum = $1; $reasonnum = ''; @@ -90,6 +95,10 @@ if ($method eq 'cancel') { $class = 'S'; $submit = "Suspend Later"; $right = 'Suspend customer package later'; +} elsif ( $method eq 'resume') { + $class = ''; + $submit = 'Unsuspend Later'; + $right = 'Unsuspend customer package'; #later? } else { die 'illegal query (unknown method param)'; } @@ -104,4 +113,7 @@ my $cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum}) my $part_pkg = $cust_pkg->part_pkg; +$date ||= $cust_pkg->get($method); +$date ||= time; + diff --git a/httemplate/misc/process/cancel_pkg.html b/httemplate/misc/process/cancel_pkg.html index a4371e6f3..662a77670 100755 --- a/httemplate/misc/process/cancel_pkg.html +++ b/httemplate/misc/process/cancel_pkg.html @@ -1,4 +1,4 @@ -<% header(emt("Package $past{$method}")) %> +<% header(emt("Package $past_method")) %> @@ -10,6 +10,7 @@ my %past = ( 'cancel' => 'cancelled', 'expire' => 'expired', 'suspend' => 'suspended', 'adjourn' => 'adjourned', + 'resume' => 'scheduled to resume', ); #i'm sure this is false laziness with somewhere, at least w/misc/cancel_pkg.html @@ -17,6 +18,7 @@ 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? ); @@ -24,8 +26,9 @@ my %right = ( 'cancel' => 'Cancel customer package immediately', #untaint method my $method = $cgi->param('method'); -$method =~ /^(cancel|expire|suspend|adjourn)$/ or die "Illegal method"; +$method =~ /^(cancel|expire|suspend|adjourn|resume)$/ or die "Illegal method"; $method = $1; +my $past_method = $past{$method}; die "access denied" unless $FS::CurrentUser::CurrentUser->access_right($right{$method}); @@ -35,30 +38,42 @@ my $pkgnum = $cgi->param('pkgnum'); $pkgnum =~ /^(\d+)$/ or die "Illegal pkgnum"; $pkgnum = $1; -#untaint reasonnum -my $reasonnum = $cgi->param('reasonnum'); -$reasonnum =~ /^(-?\d+)$/ or die "Illegal reasonnum"; -$reasonnum = $1; - my $date = time; -if ($method eq 'expire' || $method eq 'adjourn'){ +if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume'){ #untaint date - $date = $cgi->param('date'); + $date = $cgi->param('date'); #huh? parse_datetime($cgi->param('date')) =~ /^(\d+)$/ or die "Illegal date"; $date = $1; - $method = ($method eq 'expire') ? 'cancel' : 'suspend'; + $method = 'cancel' if $method eq 'expire'; + $method = 'suspend' if $method eq 'adjourn'; + $method = 'unsuspend' if $method eq 'resume'; +} + +my $resume_date; +if ( $method eq 'suspend' ) { #or 'adjourn' + $resume_date = parse_datetime($cgi->param('resume_date')) + if $cgi->param('resume_date'); } my $cust_pkg = qsearchs( 'cust_pkg', {'pkgnum'=>$pkgnum} ); -if ($reasonnum == -1) { - $reasonnum = { - 'typenum' => scalar( $cgi->param('newreasonnumT') ), - 'reason' => scalar( $cgi->param('newreasonnum' ) ), - }; +#untaint reasonnum +my $reasonnum = $cgi->param('reasonnum'); +if ( $method ne 'unsuspend' ) { #i.e. 'resume' + $reasonnum =~ /^(-?\d+)$/ or die "Illegal reasonnum"; + $reasonnum = $1; + + if ($reasonnum == -1) { + $reasonnum = { + 'typenum' => scalar( $cgi->param('newreasonnumT') ), + 'reason' => scalar( $cgi->param('newreasonnum' ) ), + }; + } } -my $error = $cust_pkg->$method( 'reason' => $reasonnum, 'date' => $date ); +my $error = $cust_pkg->$method( 'reason' => $reasonnum, + 'date' => $date, + 'resume_date' => $resume_date ); if ($error) { $cgi->param('error', $error); diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html index a5925491d..28df9da95 100644 --- a/httemplate/view/cust_main/packages/status.html +++ b/httemplate/view/cust_main/packages/status.html @@ -61,6 +61,7 @@ % if ( $part_pkg->option('suspend_bill', 1) ) { <% pkg_status_row_if( $cust_pkg, emt('Next bill'), 'bill', %opt, curuser=>$curuser ) %> % } + <% pkg_status_row_if( $cust_pkg, emt('Will resume'), 'resume', %opt, curuser=>$curuser ) %> <% pkg_status_row_if( $cust_pkg, emt('Expires'), 'expire', %opt, curuser=>$curuser ) %> <% pkg_status_row_if( $cust_pkg, emt('Contract ends'), 'contract_end', %opt ) %> @@ -69,7 +70,8 @@ % if ( $curuser->access_right('Unsuspend customer package') ) { ( <% pkg_unsuspend_link($cust_pkg) %> ) -% } + ( <% pkg_resume_link($cust_pkg) %> ) +% } % if ( $curuser->access_right('Cancel customer package immediately') ) { ( <% pkg_cancel_link($cust_pkg) %> ) % } @@ -179,6 +181,7 @@ <% pkg_status_row_if($cust_pkg, emt('Will automatically suspend by'), 'autosuspend', %opt) %> <% pkg_status_row_if($cust_pkg, emt('Automatic suspension delayed until'), 'dundate', %opt) %> <% pkg_status_row_if( $cust_pkg, emt('Will suspend on'), 'adjourn', %opt, curuser=>$curuser ) %> + <% pkg_status_row_if( $cust_pkg, emt('Will resume on'), 'resume', %opt, curuser=>$curuser ) %> <% pkg_status_row_if( $cust_pkg, emt('Expires'), 'expire', %opt, curuser=>$curuser ) %> <% pkg_status_row_if( $cust_pkg, emt('Contract ends'), 'contract_end', %opt ) %> @@ -440,7 +443,17 @@ sub pkg_delay_link { ) } -sub pkg_unsuspend_link { pkg_link('misc/unsusp_pkg', emt('Unsuspend'), @_ ); } +sub pkg_resume_link { + include( '/elements/popup_link-cust_pkg.html', + 'action' => $p. 'misc/cancel_pkg.html?method=resume', + 'label' => emt('Unsuspend later'), + 'actionlabel' => emt('Resume'), + 'color' => '#00CC00', + 'cust_pkg' => shift, + ) +} + +sub pkg_unsuspend_link { pkg_link('misc/unsusp_pkg', emt('Unsuspend now'), @_ ); } sub pkg_unadjourn_link { pkg_link('misc/unadjourn_pkg', emt('Abort'), @_ ); } sub pkg_unexpire_link { pkg_link('misc/unexpire_pkg', emt('Abort'), @_ ); } -- 2.11.0