[freeside-commits] branch master updated. 8f1fcb5c5409da6c9cfb9ff21d451853a7579db8

Mark Wells mark at 420.am
Mon Jul 15 19:00:34 PDT 2013


The branch, master has been updated
       via  8f1fcb5c5409da6c9cfb9ff21d451853a7579db8 (commit)
       via  2c62268f304f1ec6e8baf89043eb1bd1197bb9a6 (commit)
      from  53fbfad948c15a03e1939e3b81e2b5fca5796015 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 8f1fcb5c5409da6c9cfb9ff21d451853a7579db8
Author: Mark Wells <mark at freeside.biz>
Date:   Mon Jul 15 18:57:05 2013 -0700

    minor fix

diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
index 5cac458..01eaf62 100644
--- a/FS/FS/cust_pkg.pm
+++ b/FS/FS/cust_pkg.pm
@@ -2142,10 +2142,13 @@ sub change_later {
         $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);
+        $err_or_pkg->set('change_date', '');
+        $err_or_pkg->set('change_pkgnum', '');
 
         $error = $self->replace       ||
                  $err_or_pkg->replace ||
-                 $err_or_pkg->delete;
+                 $change_to->cancel   ||
+                 $change_to->delete;
       } else {
         $error = $err_or_pkg;
       }

commit 2c62268f304f1ec6e8baf89043eb1bd1197bb9a6
Author: Mark Wells <mark at freeside.biz>
Date:   Mon Jul 15 18:37:26 2013 -0700

    future package change, #20687

diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 5e2e2ef..8d234de 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 220f66a..081dd70 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 dd67d03..5cac458 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<FS::part_pkg_link>) 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<FS::part_pkg>).
 
 New refnum (see L<FS::part_referral>).
 
+=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<cust_pkg-change_svcpart> 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<abort_change>.
+
+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<change>.
+
+=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<change_later>.
+
+=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 c893f13..9d06d8e 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;
+}
 
 </%init>
diff --git a/httemplate/elements/tr-select-cust-part_pkg.html b/httemplate/elements/tr-select-cust-part_pkg.html
index c9c50d2..488f04a 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 03e336c..7425fbf 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) &>
 
 <SCRIPT TYPE="text/javascript" SRC="../elements/order_pkg.js"></SCRIPT>
-
 <& /elements/error.html &>
 
 <FORM NAME="OrderPkgForm" ACTION="<% $p %>edit/process/change-cust_pkg.html" METHOD=POST>
@@ -30,6 +29,21 @@
 
 </TABLE>
 
+<TABLE>
+  <TR>
+    <TD> Apply this change: </TD>
+    <TD> <INPUT TYPE="radio" NAME="delay" VALUE="0" \
+          <% !$cgi->param('delay') ? 'CHECKED' : '' %>> now </TD>
+    <TD> <INPUT TYPE="radio" NAME="delay" VALUE="1" \
+          <% $cgi->param('delay')  ? 'CHECKED' : '' %>> in the future
+      <& /elements/input-date-field.html, {
+  'name'  => 'start_date',
+  'value' => ($cgi->param('start_date') || $cust_main->next_bill_date),
+      } &>
+    </TD>
+  </TR>
+</TABLE>
+
 <& /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";
+}
 </%init>
diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html
index e32fe4c..566ab29 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 ab961b7..01cbc0f 100644
--- a/httemplate/view/cust_main/packages/location.html
+++ b/httemplate/view/cust_main/packages/location.html
@@ -1,6 +1,11 @@
-% if ( $default ) {
-  <DIV STYLE="font-style: italic; font-size: small">
-% }
+% if ( $cust_pkg->change_from_pkg
+%      and $cust_pkg->change_from_pkg->locationnum == $cust_pkg->locationnum )
+% {
+% # don't show the location
+% } else {
+%   if ( $default ) {
+    <DIV STYLE="font-style: italic; font-size: small">
+%   }
 
     <% $loc->location_label( 'join_string'     => '<BR>',
                              'double_space'    => '   ',
@@ -22,25 +27,25 @@
         </FONT>
 %   }
 
-% if ( $default ) {
-  </DIV>
-% }
+%   if ( $default ) {
+    </DIV>
+%   }
 
-% if ( ! $cust_pkg->get('cancel')
+%   if ( ! $cust_pkg->get('cancel')
 %      && $FS::CurrentUser::CurrentUser->access_right('Change customer package')
-%    )
-% {
+%     )
+%   {
   <BR>
   <FONT SIZE=-1>
-%   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)%> )
-%   }
+%     }
   </FONT>
-% } 
-
+%   } 
+% }
 <%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 7aad9a4..596a473 100644
--- a/httemplate/view/cust_main/packages/package.html
+++ b/httemplate/view/cust_main/packages/package.html
@@ -1,5 +1,4 @@
-<TD CLASS="inv package" BGCOLOR="<% $bgcolor %>" VALIGN="top"
-  STYLE="border-left-width: <% $supplemental * 30 %>px">
+<TD CLASS="inv package" BGCOLOR="<% $bgcolor %>" VALIGN="top" <%$style%>>
   <TABLE CLASS="inv package"> 
     <TR>
       <TD COLSPAN=2>
@@ -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 @@
 %        )
 %     {
       <TR>
+% # yeah, I guess we'll let you do this on a future change package
 %       if ( FS::Conf->new->exists('invoice-unitprice') ) {
         <TD><FONT SIZE="-1">
           ( <% 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 82d0620..0383fe8 100755
--- a/httemplate/view/cust_main/packages/section.html
+++ b/httemplate/view/cust_main/packages/section.html
@@ -36,6 +36,10 @@
     <& services.html, %iopt &>
   </TR>
 % $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 ed360cc..6894a4e 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 ) {
       <TR>
         <TD COLSPAN=<%$opt{colspan}%>>
           <FONT SIZE=-1>
+%           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) %> )
+                <BR>
+%               }
+%           }
 %           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) %> )
 %           } 
           </FONT>
@@ -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 @@
         <TR>
           <TD COLSPAN=<%$opt{colspan}%>>
             <FONT SIZE=-1>
-%             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) %> )
+                <BR>
+%               }
+%           }
+
+%           # 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
+              <BR>
 %             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) %> )
 %             } 
+%           }
 
             <FONT>
           </TD>
@@ -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 = '<FONT SIZE=-1>( '. pkg_unexpire_link($cust_pkg). ' ) </FONT>'. $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 <b>[_1]</b> on',
+                 $cust_pkg->change_to_pkg->part_pkg->pkg);
+    } elsif ( $cust_pkg->change_to_pkg->locationnum != $cust_pkg->locationnum )
+    {
+      $title = mt('Will <b>change location</b> on');
+    } else {
+      # FS::cust_pkg->change_later should have prevented this, but 
+      # just so that we can display _something_
+      $title = '<font color="#ff0000">Unknown package change</font>';
+    }
+
+  } else {
+
+    $title = emt('Expires');
+    if ( $opt{curuser}->access_right('Cancel customer package later')) {
+      $title = '<FONT SIZE=-1>( '. pkg_unexpire_link($cust_pkg). ' ) </FONT>'. $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',

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/Schema.pm                                  |    1 +
 FS/FS/cust_main/Billing.pm                       |   20 ++-
 FS/FS/cust_pkg.pm                                |  330 +++++++++++++++++-----
 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, 508 insertions(+), 119 deletions(-)




More information about the freeside-commits mailing list