From 2c62268f304f1ec6e8baf89043eb1bd1197bb9a6 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Mon, 15 Jul 2013 18:37:26 -0700 Subject: [PATCH] future package change, #20687 --- FS/FS/Schema.pm | 1 + FS/FS/cust_main/Billing.pm | 20 +- FS/FS/cust_pkg.pm | 327 ++++++++++++++++++----- httemplate/edit/process/change-cust_pkg.html | 31 ++- httemplate/elements/tr-select-cust-part_pkg.html | 5 +- httemplate/misc/change_pkg.cgi | 29 +- httemplate/view/cust_main/packages.html | 21 +- httemplate/view/cust_main/packages/location.html | 35 +-- httemplate/view/cust_main/packages/package.html | 26 +- httemplate/view/cust_main/packages/section.html | 4 + httemplate/view/cust_main/packages/status.html | 125 +++++++-- 11 files changed, 505 insertions(+), 119 deletions(-) diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 5e2e2efd5..8d234de81 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -1798,6 +1798,7 @@ sub tables_hashref { 'waive_setup', 'char', 'NULL', 1, '', '', 'recur_show_zero', 'char', 'NULL', 1, '', '', 'setup_show_zero', 'char', 'NULL', 1, '', '', + 'change_to_pkgnum', 'int', 'NULL', '', '', '', ], 'primary_key' => 'pkgnum', 'unique' => [], diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm index 220f66a0c..081dd70f7 100644 --- a/FS/FS/cust_main/Billing.pm +++ b/FS/FS/cust_main/Billing.pm @@ -192,14 +192,30 @@ sub cancel_expired_pkgs { my @errors = (); - foreach my $cust_pkg ( @cancel_pkgs ) { + CUST_PKG: foreach my $cust_pkg ( @cancel_pkgs ) { my $cpr = $cust_pkg->last_cust_pkg_reason('expire'); - my $error = $cust_pkg->cancel($cpr ? ( 'reason' => $cpr->reasonnum, + my $error; + + if ( $cust_pkg->change_to_pkgnum ) { + + my $new_pkg = FS::cust_pkg->by_key($cust_pkg->change_to_pkgnum); + if ( !$new_pkg ) { + push @errors, 'can\'t change pkgnum '.$cust_pkg->pkgnum.' to pkgnum '. + $cust_pkg->change_to_pkgnum.'; not expiring'; + next CUST_PKG; + } + $error = $cust_pkg->change( 'cust_pkg' => $new_pkg, + 'unprotect_svcs' => 1 ); + $error = '' if ref $error eq 'FS::cust_pkg'; + + } else { # just cancel it + $error = $cust_pkg->cancel($cpr ? ( 'reason' => $cpr->reasonnum, 'reason_otaker' => $cpr->otaker, 'time' => $time, ) : () ); + } push @errors, 'pkgnum '.$cust_pkg->pkgnum.": $error" if $error; } diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index dd67d0313..5cac458ca 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -210,6 +210,11 @@ The pkgnum of the package that this package is supplemental to, if any. The package link (L) that defines this supplemental package, if it is one. +=item change_to_pkgnum + +The pkgnum of the package this one will be "changed to" in the future +(on its expiration date). + =back Note: setup, last_bill, bill, adjourn, susp, expire, cancel and change_date @@ -353,15 +358,6 @@ sub insert { } } - #if ( $self->reg_code ) { - # my $reg_code = qsearchs('reg_code', { 'code' => $self->reg_code } ); - # $error = $reg_code->delete; - # if ( $error ) { - # $dbh->rollback if $oldAutoCommit; - # return $error; - # } - #} - my $conf = new FS::Conf; if ( $conf->config('ticket_system') && $options{ticket_subject} ) { @@ -651,6 +647,7 @@ sub check { || $self->ut_enum('setup_show_zero', [ '', 'Y', 'N', ]) || $self->ut_foreign_keyn('main_pkgnum', 'cust_pkg', 'pkgnum') || $self->ut_foreign_keyn('pkglinknum', 'part_pkg_link', 'pkglinknum') + || $self->ut_foreign_keyn('change_to_pkgnum', 'cust_pkg', 'pkgnum') ; return $error if $error; @@ -872,10 +869,19 @@ sub cancel { } #unless $date my %hash = $self->hash; - $date ? ($hash{'expire'} = $date) : ($hash{'cancel'} = $cancel_time); + if ( $date ) { + $hash{'expire'} = $date; + } else { + $hash{'cancel'} = $cancel_time; + } $hash{'change_custnum'} = $options{'change_custnum'}; + my $new = new FS::cust_pkg ( \%hash ); $error = $new->replace( $self, options => { $self->options } ); + if ( $self->change_to_pkgnum ) { + my $change_to = FS::cust_pkg->by_key($self->change_to_pkgnum); + $error ||= $change_to->cancel || $change_to->delete; + } if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -1728,15 +1734,27 @@ New pkgpart (see L). New refnum (see L). +=item cust_pkg + +"New" (existing) FS::cust_pkg object. The package's services and other +attributes will be transferred to this package. + =item keep_dates Set to true to transfer billing dates (start_date, setup, last_bill, bill, susp, adjourn, cancel, expire, and contract_end) to the new package. +=item unprotect_svcs + +Normally, change() will rollback and return an error if some services +can't be transferred (also see the I config option). +If unprotect_svcs is true, this method will transfer as many services as +it can and then unconditionally cancel the old package. + =back -At least one of locationnum, cust_location, pkgpart, refnum must be specified -(otherwise, what's the point?) +At least one of locationnum, cust_location, pkgpart, refnum, cust_main, or +cust_pkg must be specified (otherwise, what's the point?) Returns either the new FS::cust_pkg object or a scalar error. @@ -1793,6 +1811,12 @@ sub change { $opt->{'locationnum'} = $opt->{'cust_location'}->locationnum; } + if ( $opt->{'cust_pkg'} ) { + # treat changing to a package with a different pkgpart as a + # pkgpart change (because it is) + $opt->{'pkgpart'} = $opt->{'cust_pkg'}->pkgpart; + } + # whether to override pkgpart checking on the new package my $same_pkgpart = 1; if ( $opt->{'pkgpart'} and ( $opt->{'pkgpart'} != $self->pkgpart ) ) { @@ -1844,16 +1868,30 @@ sub change { $hash{'contactnum'} = $opt->{'contactnum'} if $opt->{'contactnum'}; - # Create the new package. - my $cust_pkg = new FS::cust_pkg { - custnum => $custnum, - pkgpart => ( $opt->{'pkgpart'} || $self->pkgpart ), - refnum => ( $opt->{'refnum'} || $self->refnum ), - locationnum => ( $opt->{'locationnum'} ), - %hash, - }; - $error = $cust_pkg->insert( 'change' => 1, - 'allow_pkgpart' => $same_pkgpart ); + my $cust_pkg; + if ( $opt->{'cust_pkg'} ) { + # The target package already exists; update it to show that it was + # changed from this package. + $cust_pkg = $opt->{'cust_pkg'}; + + foreach ( qw( pkgnum pkgpart locationnum ) ) { + $cust_pkg->set("change_$_", $self->get($_)); + } + $cust_pkg->set('change_date', $time); + $error = $cust_pkg->replace; + + } else { + # Create the new package. + $cust_pkg = new FS::cust_pkg { + custnum => $custnum, + pkgpart => ( $opt->{'pkgpart'} || $self->pkgpart ), + refnum => ( $opt->{'refnum'} || $self->refnum ), + locationnum => ( $opt->{'locationnum'} ), + %hash, + }; + $error = $cust_pkg->insert( 'change' => 1, + 'allow_pkgpart' => $same_pkgpart ); + } if ($error) { $dbh->rollback if $oldAutoCommit; return $error; @@ -1878,7 +1916,11 @@ sub change { } } - if ($error > 0) { + # We set unprotect_svcs when executing a "future package change". It's + # not a user-interactive operation, so returning an error means the + # package change will just fail. Rather than have that happen, we'll + # let leftover services be deleted. + if ($error > 0 and !$opt->{'unprotect_svcs'}) { # Transfers were successful, but we still had services left on the old # package. We can't change the package under this circumstances, so abort. $dbh->rollback if $oldAutoCommit; @@ -1939,57 +1981,62 @@ sub change { return "Error transferring package notes: $error"; } } - - # Order any supplemental packages. - my $part_pkg = $cust_pkg->part_pkg; - my @old_supp_pkgs = $self->supplemental_pkgs; + my @new_supp_pkgs; - foreach my $link ($part_pkg->supp_part_pkg_link) { - my $old; - foreach (@old_supp_pkgs) { - if ($_->pkgpart == $link->dst_pkgpart) { - $old = $_; - $_->pkgpart(0); # so that it can't match more than once + + if ( !$opt->{'cust_pkg'} ) { + # Order any supplemental packages. + my $part_pkg = $cust_pkg->part_pkg; + my @old_supp_pkgs = $self->supplemental_pkgs; + foreach my $link ($part_pkg->supp_part_pkg_link) { + my $old; + foreach (@old_supp_pkgs) { + if ($_->pkgpart == $link->dst_pkgpart) { + $old = $_; + $_->pkgpart(0); # so that it can't match more than once + } + last if $old; } - last if $old; - } - # false laziness with FS::cust_main::Packages::order_pkg - my $new = FS::cust_pkg->new({ - pkgpart => $link->dst_pkgpart, - pkglinknum => $link->pkglinknum, - custnum => $custnum, - main_pkgnum => $cust_pkg->pkgnum, - locationnum => $cust_pkg->locationnum, - start_date => $cust_pkg->start_date, - order_date => $cust_pkg->order_date, - expire => $cust_pkg->expire, - adjourn => $cust_pkg->adjourn, - contract_end => $cust_pkg->contract_end, - refnum => $cust_pkg->refnum, - discountnum => $cust_pkg->discountnum, - waive_setup => $cust_pkg->waive_setup, - }); - if ( $old and $opt->{'keep_dates'} ) { - foreach (qw(setup bill last_bill)) { - $new->set($_, $old->get($_)); + # false laziness with FS::cust_main::Packages::order_pkg + my $new = FS::cust_pkg->new({ + pkgpart => $link->dst_pkgpart, + pkglinknum => $link->pkglinknum, + custnum => $custnum, + main_pkgnum => $cust_pkg->pkgnum, + locationnum => $cust_pkg->locationnum, + start_date => $cust_pkg->start_date, + order_date => $cust_pkg->order_date, + expire => $cust_pkg->expire, + adjourn => $cust_pkg->adjourn, + contract_end => $cust_pkg->contract_end, + refnum => $cust_pkg->refnum, + discountnum => $cust_pkg->discountnum, + waive_setup => $cust_pkg->waive_setup, + }); + if ( $old and $opt->{'keep_dates'} ) { + foreach (qw(setup bill last_bill)) { + $new->set($_, $old->get($_)); + } } + $error = $new->insert( allow_pkgpart => $same_pkgpart ); + # transfer services + if ( $old ) { + $error ||= $old->transfer($new); + } + if ( $error and $error > 0 ) { + # no reason why this should ever fail, but still... + $error = "Unable to transfer all services from supplemental package ". + $old->pkgnum; + } + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + push @new_supp_pkgs, $new; } - $error = $new->insert( allow_pkgpart => $same_pkgpart ); - # transfer services - if ( $old ) { - $error ||= $old->transfer($new); - } - if ( $error and $error > 0 ) { - # no reason why this should ever fail, but still... - $error = "Unable to transfer all services from supplemental package ". - $old->pkgnum; - } - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - push @new_supp_pkgs, $new; - } + } # if !$opt->{'cust_pkg'} + # because if there is one, then supplemental packages would already + # have been created for it. #Good to go, cancel old package. Notify 'cancel' of whether to credit #remaining time. @@ -1997,6 +2044,11 @@ sub change { #outstanding usage) if we are keeping dates (i.e. location changing), #because the new package will be billed for the same date range. #Supplemental packages are also canceled here. + + # during scheduled changes, avoid canceling the package we just + # changed to (duh) + $self->set('change_to_pkgnum' => ''); + $error = $self->cancel( quiet => 1, unused_credit => $unused_credit, @@ -2025,6 +2077,141 @@ sub change { } +=item change_later OPTION => VALUE... + +Schedule a package change for a later date. This actually orders the new +package immediately, but sets its start date for a future date, and sets +the current package to expire on the same date. + +If the package is already scheduled for a change, this can be called with +'start_date' to change the scheduled date, or with pkgpart and/or +locationnum to modify the package change. To cancel the scheduled change +entirely, see C. + +Options include: + +=over 4 + +=item start_date + +The date for the package change. Required, and must be in the future. + +=item pkgpart + +=item locationnum + +The pkgpart and locationnum of the new package, with the same +meaning as in C. + +=back + +=cut + +sub change_later { + my $self = shift; + my $opt = ref($_[0]) ? shift : { @_ }; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $cust_main = $self->cust_main; + + my $date = delete $opt->{'start_date'} or return 'start_date required'; + + if ( $date <= time ) { + $dbh->rollback if $oldAutoCommit; + return "start_date $date is in the past"; + } + + my $error; + + if ( $self->change_to_pkgnum ) { + my $change_to = FS::cust_pkg->by_key($self->change_to_pkgnum); + my $new_pkgpart = $opt->{'pkgpart'} + if $opt->{'pkgpart'} and $opt->{'pkgpart'} != $change_to->pkgpart; + my $new_locationnum = $opt->{'locationnum'} + if $opt->{'locationnum'} and $opt->{'locationnum'} != $change_to->locationnum; + if ( $new_pkgpart or $new_locationnum ) { + # it hasn't been billed yet, so in principle we could just edit + # it in place (w/o a package change), but that's bad form. + # So change the package according to the new options... + my $err_or_pkg = $change_to->change(%$opt); + if ( ref $err_or_pkg ) { + # Then set that package up for a future start. + $self->set('change_to_pkgnum', $err_or_pkg->pkgnum); + $self->set('expire', $date); # in case it's different + $err_or_pkg->set('start_date', $date); + + $error = $self->replace || + $err_or_pkg->replace || + $err_or_pkg->delete; + } else { + $error = $err_or_pkg; + } + } else { # change the start date only. + $self->set('expire', $date); + $change_to->set('start_date', $date); + $error = $self->replace || $change_to->replace; + } + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } else { + $dbh->commit if $oldAutoCommit; + return ''; + } + } # if $self->change_to_pkgnum + + my $new_pkgpart = $opt->{'pkgpart'} + if $opt->{'pkgpart'} and $opt->{'pkgpart'} != $self->pkgpart; + my $new_locationnum = $opt->{'locationnum'} + if $opt->{'locationnum'} and $opt->{'locationnum'} != $self->locationnum; + return '' unless $new_pkgpart or $new_locationnum; # wouldn't do anything + + my %hash = ( + 'custnum' => $self->custnum, + 'pkgpart' => ($opt->{'pkgpart'} || $self->pkgpart), + 'locationnum' => ($opt->{'locationnum'} || $self->locationnum), + 'start_date' => $date, + ); + my $new = FS::cust_pkg->new(\%hash); + $error = $new->insert('change' => 1, + 'allow_pkgpart' => ($new_pkgpart ? 0 : 1)); + if ( !$error ) { + $self->set('change_to_pkgnum', $new->pkgnum); + $self->set('expire', $date); + $error = $self->replace; + } + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + } else { + $dbh->commit if $oldAutoCommit; + } + + $error; +} + +=item abort_change + +Cancels a future package change scheduled by C. + +=cut + +sub abort_change { + my $self = shift; + my $pkgnum = $self->change_to_pkgnum; + my $change_to = FS::cust_pkg->by_key($pkgnum) if $pkgnum; + my $error; + if ( $change_to ) { + $error = $change_to->cancel || $change_to->delete; + return $error if $error; + } + $self->set('change_to_pkgnum', ''); + $self->set('expire', ''); + $self->replace; +} + =item set_quantity QUANTITY Change the package's quantity field. This is the one package property diff --git a/httemplate/edit/process/change-cust_pkg.html b/httemplate/edit/process/change-cust_pkg.html index c893f13a2..9d06d8e1a 100644 --- a/httemplate/edit/process/change-cust_pkg.html +++ b/httemplate/edit/process/change-cust_pkg.html @@ -40,8 +40,35 @@ if ( $cgi->param('locationnum') == -1 ) { $change{'cust_location'} = $cust_location; } -my $pkg_or_error = $cust_pkg->change( \%change ); +my $error; +if ( $cgi->param('delay') ) { + my $date = parse_datetime($cgi->param('start_date')); + if (!$date) { + $error = "Invalid change date '".$cgi->param('start_date')."'."; + } elsif ( $date < time ) { + $error = "Change date ".$cgi->param('start_date')." is in the past."; + } else { + # schedule the change + $change{'start_date'} = $date; + $error = $cust_pkg->change_later(\%change); + } +} else { + # special case: if there's a package change scheduled, and it matches + # the parameters the user requested this time, then change to the existing + # future package. + if ( $cust_pkg->change_to_pkgnum ) { + my $change_to = FS::cust_pkg->by_key($cust_pkg->change_to_pkgnum); + if ( $change_to->pkgpart == $change{'pkgpart'} and + $change_to->locationnum == $change{'locationnum'} ) { -my $error = ref($pkg_or_error) ? '' : $pkg_or_error; + %change = ( 'cust_pkg' => $change_to ); + + } + } + + # do a package change right now + my $pkg_or_error = $cust_pkg->change( \%change ); + $error = ref($pkg_or_error) ? '' : $pkg_or_error; +} diff --git a/httemplate/elements/tr-select-cust-part_pkg.html b/httemplate/elements/tr-select-cust-part_pkg.html index c9c50d27e..488f04a13 100644 --- a/httemplate/elements/tr-select-cust-part_pkg.html +++ b/httemplate/elements/tr-select-cust-part_pkg.html @@ -20,7 +20,7 @@ what.form.pkgpart.disabled = 'disabled'; //disable part_pkg dropdown var submitButton = what.form.submitButton; // || what.form.submit; - if ( submitButton ) { + if ( submitButton && <% $opt{'curr_value'} ? 0 : 1 %> ) { submitButton.disabled = true; //disable the submit button } var discountnum = what.form.discountnum; @@ -51,6 +51,9 @@ } what.form.pkgpart.disabled = ''; //re-enable part_pkg dropdown +% if ( $opt{'curr_value'} ) { + what.form.pkgpart.value = <% $opt{'curr_value'} %>; +% } } diff --git a/httemplate/misc/change_pkg.cgi b/httemplate/misc/change_pkg.cgi index 03e336cba..7425fbfaf 100755 --- a/httemplate/misc/change_pkg.cgi +++ b/httemplate/misc/change_pkg.cgi @@ -1,7 +1,6 @@ -<& /elements/header-popup.html, mt("Change Package") &> +<& /elements/header-popup.html, mt($title) &> - <& /elements/error.html &>
@@ -30,6 +29,21 @@ + + + + + + +
Apply this change: param('delay') ? 'CHECKED' : '' %>> now param('delay') ? 'CHECKED' : '' %>> in the future + <& /elements/input-date-field.html, { + 'name' => 'start_date', + 'value' => ($cgi->param('start_date') || $cust_main->next_bill_date), + } &> +
+ <& /elements/standardize_locations.html, 'form' => "OrderPkgForm", 'callback' => 'document.OrderPkgForm.submit();', @@ -74,4 +88,15 @@ my $cust_main = $cust_pkg->cust_main my $part_pkg = $cust_pkg->part_pkg; +my $title = "Change Package"; + +# if there's already a package change ordered, preload it +if ( $cust_pkg->change_to_pkgnum ) { + my $change_to = FS::cust_pkg->by_key($cust_pkg->change_to_pkgnum); + $cgi->param('delay', 1); + foreach(qw( start_date pkgpart locationnum )) { + $cgi->param($_, $change_to->get($_)); + } + $title = "Edit Scheduled Package Change"; +} diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html index e32fe4c03..566ab2943 100755 --- a/httemplate/view/cust_main/packages.html +++ b/httemplate/view/cust_main/packages.html @@ -3,7 +3,6 @@ td.package { vertical-align: top; border-width: 0; border-style: solid; - border-color: #bbbbff; } table.package { border: none; @@ -199,11 +198,30 @@ sub get_packages { } ); my $num_old_packages = scalar(@packages); + my %change_to_from; # target pkgnum => current cust_pkg, for future changes + foreach my $cust_pkg ( @packages ) { my %hash = $cust_pkg->hash; my %part_pkg = map { /^part_pkg_(.+)$/ or die; ( $1 => $hash{$_} ); } grep { /^part_pkg_/ } keys %hash; $cust_pkg->{'_pkgpart'} = new FS::part_pkg \%part_pkg; + if ( $cust_pkg->change_to_pkgnum ) { + $change_to_from{$cust_pkg->change_to_pkgnum} = $cust_pkg; + } + } + + if ( keys %change_to_from ) { + my @not_future_packages; + foreach my $cust_pkg (@packages) { + if ( exists( $change_to_from{$cust_pkg->pkgnum} ) ) { + my $change_from = $change_to_from{ $cust_pkg->pkgnum }; + $cust_pkg->set('change_from_pkg', $change_from); + $change_from->set('change_to_pkg', $cust_pkg); + } else { + push @not_future_packages, $cust_pkg; + } + } + @packages = @not_future_packages; } unless ( $cgi->param('showoldpackages') ) { @@ -225,6 +243,7 @@ sub get_packages { # don't include supplemental packages in this list; they'll be found from # their main packages + # (as will change-target packages) @packages = grep !$_->main_pkgnum, @packages; ( \@packages, $num_old_packages ); diff --git a/httemplate/view/cust_main/packages/location.html b/httemplate/view/cust_main/packages/location.html index ab961b79e..01cbc0ffb 100644 --- a/httemplate/view/cust_main/packages/location.html +++ b/httemplate/view/cust_main/packages/location.html @@ -1,6 +1,11 @@ -% if ( $default ) { -
-% } +% if ( $cust_pkg->change_from_pkg +% and $cust_pkg->change_from_pkg->locationnum == $cust_pkg->locationnum ) +% { +% # don't show the location +% } else { +% if ( $default ) { +
+% } <% $loc->location_label( 'join_string' => '
', 'double_space' => '   ', @@ -22,25 +27,25 @@ % } -% if ( $default ) { -
-% } +% if ( $default ) { +
+% } -% if ( ! $cust_pkg->get('cancel') +% if ( ! $cust_pkg->get('cancel') % && $FS::CurrentUser::CurrentUser->access_right('Change customer package') -% ) -% { +% ) +% {
-% unless ( $opt{no_links} ) { +% unless ( $opt{no_links} or $opt{'change_from'} ) { ( <%pkg_change_location_link($cust_pkg)%> ) -% } -% if ( $cust_pkg->locationnum && ! $opt{no_links} ) { +% } +% if ( $cust_pkg->locationnum && ! $opt{no_links} ) { ( <%edit_location_link($cust_pkg->locationnum)%> ) -% } +% } -% } - +% } +% } <%init> my $conf = new FS::Conf; diff --git a/httemplate/view/cust_main/packages/package.html b/httemplate/view/cust_main/packages/package.html index 7aad9a44e..596a47391 100644 --- a/httemplate/view/cust_main/packages/package.html +++ b/httemplate/view/cust_main/packages/package.html @@ -1,5 +1,4 @@ - +> +% # yeah, I guess we'll let you do this on a future change package % if ( FS::Conf->new->exists('invoice-unitprice') ) { % $row++; +% # show the change target, if there is one +% if ( $cust_pkg->change_to_pkg ) { + <& .packagerow, $cust_pkg->change_to_pkg, %iopt, 'change_from' => 1 &> +% } % # include supplemental packages if any % $iopt{'supplemental'} = ($iopt{'supplemental'} || 0) + 1; % foreach my $supp_pkg ($cust_pkg->supplemental_pkgs) { diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html index ed360cca4..6894a4e02 100644 --- a/httemplate/view/cust_main/packages/status.html +++ b/httemplate/view/cust_main/packages/status.html @@ -76,18 +76,29 @@ <% 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_expire($cust_pkg, %opt, curuser=>$curuser) %> <% pkg_status_row_if( $cust_pkg, emt('Contract ends'), 'contract_end', %opt ) %> -% if ( !$supplemental && ! $opt{no_links} ) { +% if ( !$supplemental && ! $opt{no_links} && !$change_from ) { @@ -251,6 +290,7 @@ my $part_pkg = $opt{'part_pkg'}; my $curuser = $FS::CurrentUser::CurrentUser; my $width = $opt{'cust_pkg-display_times'} ? '38%' : '56%'; my $supplemental = $opt{'supplemental'}; +my $change_from = $opt{'change_from'}; $opt{colspan} = $opt{'cust_pkg-display_times'} ? 8 : 4; @@ -330,14 +370,41 @@ sub pkg_status_row_if { $opt{curuser}->access_right('Suspend customer package later') ); - $title = '( '. pkg_unexpire_link($cust_pkg). ' ) '. $title - if ( $field eq 'expire' && - $opt{curuser}->access_right('Cancel customer package later') - ); - $cust_pkg->get($field) ? pkg_status_row($cust_pkg, $title, $field, %opt) : ''; } +sub pkg_status_row_expire { + my $cust_pkg = shift; + my %opt = @_; + return unless $cust_pkg->get('expire'); + + my $title; + + if ( $cust_pkg->get('change_to_pkg') ) { + if ( $cust_pkg->change_to_pkg->pkgpart != $cust_pkg->pkgpart ) { + $title = mt('Will change to [_1] on', + $cust_pkg->change_to_pkg->part_pkg->pkg); + } elsif ( $cust_pkg->change_to_pkg->locationnum != $cust_pkg->locationnum ) + { + $title = mt('Will change location on'); + } else { + # FS::cust_pkg->change_later should have prevented this, but + # just so that we can display _something_ + $title = 'Unknown package change'; + } + + } else { + + $title = emt('Expires'); + if ( $opt{curuser}->access_right('Cancel customer package later')) { + $title = '( '. pkg_unexpire_link($cust_pkg). ' ) '. $title; + } + + } + + pkg_status_row( $cust_pkg, $title, 'expire', %opt ); +} + sub pkg_status_row_changed { my( $cust_pkg, %opt ) = @_; @@ -538,6 +605,8 @@ sub pkg_resume_link { 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'), @_ ); } +sub pkg_unchange_link { pkg_link('misc/do_not_change_pkg', emt('Abort change'), @_ ); } +sub pkg_change_now_link { pkg_link('misc/change_pkg_now', emt('Change now'), @_ ); } sub pkg_cancel_link { include( '/elements/popup_link-cust_pkg.html', @@ -569,6 +638,18 @@ sub pkg_expire_link { ) } +sub pkg_change_later_link { + my $cust_pkg = shift; + include( '/elements/popup_link-cust_pkg.html', + 'action' => $p . 'misc/change_pkg.cgi?', + 'label' => emt('Reschedule'), + 'actionlabel' => emt('Edit scheduled change for'), + 'cust_pkg' => $cust_pkg, + 'width' => 763, + 'height' => 480, + ) +} + sub svc_recharge_link { include( '/elements/popup_link-cust_svc.html', 'action' => $p. 'misc/recharge_svc.html', -- 2.11.0
@@ -30,7 +29,11 @@ % unless ( $cust_pkg->get('cancel') || $opt{no_links} ) { % -% if ( $supplemental or $part_pkg->freq eq '0' ) { +% if ( $change_from ) { +% # This is the target package for a future change. +% # Nothing you can do with it besides modify/cancel the +% # future change, and that's on the current package. +% } elsif ( $supplemental or $part_pkg->freq eq '0' ) { % # Supplemental packages can't be changed independently. % # One-time charges don't need to be changed. % # For both of those, we only show "Add comments", @@ -185,6 +188,7 @@ % ) % {
( <% pkg_change_quantity_link($cust_pkg) %> ) @@ -233,7 +237,21 @@ my $countrydefault = $opt{'countrydefault'} || 'US'; my $statedefault = $opt{'statedefault'} || ($countrydefault eq 'US' ? 'CA' : ''); +# put a marker on the left edge of this column +# if this package is somehow special my $supplemental = $opt{'supplemental'} || 0; +my $change_from = $opt{'change_from'} || 0; +my $style = ''; +if ( $supplemental or $change_from ) { + $style = 'border-left-width: '.($supplemental + $change_from)*30 . 'px; '. + 'border-color: '; + if ( $supplemental ) { + $style .= '#bbbbff'; + } elsif ( $change_from ) { + $style .= '#bbffbb'; + } + $style = qq!STYLE="$style"!; +} $cust_pkg->pkgnum =~ /^(\d+)$/; my $pkgnum = $1; @@ -263,7 +281,7 @@ sub pkg_change_link { 'actionlabel' => emt('Change'), 'cust_pkg' => $cust_pkg, 'width' => 763, - 'height' => 380, + 'height' => 480, ); } diff --git a/httemplate/view/cust_main/packages/section.html b/httemplate/view/cust_main/packages/section.html index 82d06203b..0383fe892 100755 --- a/httemplate/view/cust_main/packages/section.html +++ b/httemplate/view/cust_main/packages/section.html @@ -36,6 +36,10 @@ <& services.html, %iopt &>
> +% if ( $cust_pkg->change_to_pkgnum ) { +% # then you can modify the package change +% if ( $curuser->access_right('Change customer package') ) { + ( <% pkg_change_now_link($cust_pkg) %> ) + ( <% pkg_change_later_link($cust_pkg) %> ) + ( <% pkg_unchange_link($cust_pkg) %> ) +
+% } +% } % 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') ) { +% if ( !$cust_pkg->change_to_pkgnum and +% $curuser->access_right('Cancel customer package immediately') +% ) { ( <% pkg_cancel_link($cust_pkg) %> ) % }
@@ -97,9 +108,17 @@ % % } else { #status: active % -% unless ( $cust_pkg->get('setup') ) { #not setup +% if ( $change_from ) { # future change +% + <% pkg_status_row_colspan( $cust_pkg, emt('Waiting for package change'), '', %opt ) %> + <% pkg_status_row( $cust_pkg, + emt('Will be activated on'), + 'start_date', + %opt ) %> % -% unless ( $part_pkg->freq ) { +% } elsif ( ! $cust_pkg->get('setup') ) { # not setup +% +% unless ( $part_pkg->freq ) { # one-time charge <% pkg_status_row_colspan( $cust_pkg, emt('Not yet billed (one-time charge)'), '', %opt ) %> @@ -193,7 +212,7 @@ % } % -% } +% } % % if ( $opt{'cust_pkg-show_autosuspend'} ) { % my $autosuspend = pkg_autosuspend_time( $cust_pkg ); @@ -207,7 +226,7 @@ <% 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_expire($cust_pkg, %opt, curuser=>$curuser) %> <% pkg_status_row_if( $cust_pkg, emt('Contract ends'), 'contract_end', %opt ) %> % if ( $part_pkg->freq and !$supplemental && ! $opt{no_links} ) { @@ -215,21 +234,41 @@
> -% if ( $curuser->access_right('Suspend customer package') ) { - ( <% pkg_suspend_link($cust_pkg) %> ) -% } -% if ( $curuser->access_right('Suspend customer package later') ) { - ( <% pkg_adjourn_link($cust_pkg) %> ) -% } -% if ( $curuser->access_right('Delay suspension events') ) { - ( <% pkg_delay_link($cust_pkg) %> ) -% } +% # action links +% if ( $change_from ) { +% # nothing +% } elsif ( $cust_pkg->change_to_pkgnum ) { +% # then you can modify the package change +% if ( $curuser->access_right('Change customer package') ) { + ( <% pkg_change_now_link($cust_pkg) %> ) + ( <% pkg_change_later_link($cust_pkg) %> ) + ( <% pkg_unchange_link($cust_pkg) %> ) +
+% } +% } + +% # suspension actions--always available +% if ( $curuser->access_right('Suspend customer package') ) { + ( <% pkg_suspend_link($cust_pkg) %> ) +% } +% if ( $curuser->access_right('Suspend customer package later') ) { + ( <% pkg_adjourn_link($cust_pkg) %> ) +% } +% if ( $curuser->access_right('Delay suspension events') ) { + ( <% pkg_delay_link($cust_pkg) %> ) +% } +% +% if ( $change_from or $cust_pkg->change_to_pkgnum ) { +% # you can't cancel the package while in this state +% } else { # the normal case: links to cancel the package +
% if ( $curuser->access_right('Cancel customer package immediately') ) { ( <% pkg_cancel_link($cust_pkg) %> ) -% } +% } % if ( $curuser->access_right('Cancel customer package later') ) { ( <% pkg_expire_link($cust_pkg) %> ) % } +% }