RT#30705 Change contract end date when changing packages
authorJonathan Prykop <jonathan@freeside.biz>
Tue, 16 Jun 2015 04:37:48 +0000 (23:37 -0500)
committerJonathan Prykop <jonathan@freeside.biz>
Tue, 16 Jun 2015 04:37:48 +0000 (23:37 -0500)
FS/FS/cust_pkg.pm
httemplate/edit/process/change-cust_pkg.html
httemplate/misc/change_pkg.cgi
httemplate/view/cust_main/packages/status.html

index 91a5677..5bd307b 100644 (file)
@@ -1943,6 +1943,13 @@ 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.
 
+=item contract_end
+
+If specified, sets this value for the contract_end date on the new package 
+(without regard for keep_dates or the usual date-preservation behavior.)
+Will throw an error if defined but false;  the UI doesn't allow editing 
+this unless it already exists, making removal impossible to undo.
+
 =back
 
 At least one of locationnum, cust_location, pkgpart, refnum, cust_main, or
@@ -1956,6 +1963,36 @@ For example:
 
 =cut
 
+#used by change and change_later
+#didn't put with documented check methods because it depends on change-specific opts
+#and it also possibly edits the value of opts
+sub _check_change {
+  my $self = shift;
+  my $opt = shift;
+  if ( defined($opt->{'contract_end'}) ) {
+    my $current_contract_end = $self->get('contract_end');
+    unless ($opt->{'contract_end'}) {
+      if ($current_contract_end) {
+        return "Cannot remove contract end date when changing packages";
+      } else {
+        #shouldn't even pass this option if there's not a current value
+        #but can be handled gracefully if the option is empty
+        warn "Contract end date passed unexpectedly";
+        delete $opt->{'contract_end'};
+        return '';
+      }
+    }
+    unless ($current_contract_end) {
+      #option shouldn't be passed, throw error if it's non-empty
+      return "Cannot add contract end date when changing packages " . $self->pkgnum;
+    }
+    if ($opt->{'start_date'} && ($opt->{'contract_end'} < $opt->{'start_date'})) {
+      return "Contract end date is before change date";
+    }
+  }
+  return '';
+}
+
 #some false laziness w/order
 sub change {
   my $self = shift;
@@ -1963,13 +2000,21 @@ sub change {
 
   my $conf = new FS::Conf;
 
+  # handle contract_end on cust_pkg same as passed option
+  if ( $opt->{'cust_pkg'} ) {
+    $opt->{'contract_end'} = $opt->{'cust_pkg'}->contract_end;
+    delete $opt->{'contract_end'} unless $opt->{'contract_end'};
+  }
+
+  # check contract_end, prevent adding/removing
+  my $error = $self->_check_change($opt);
+  return $error if $error;
+
   # Transactionize this whole mess
   my $oldAutoCommit = $FS::UID::AutoCommit;
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
-  my $error;
-
   if ( $opt->{'cust_location'} ) {
     $error = $opt->{'cust_location'}->find_or_insert;
     if ( $error ) {
@@ -1994,6 +2039,9 @@ sub change {
     if ( $opt->{'pkgpart'} and $opt->{'pkgpart'} != $self->pkgpart ) {
       $self->set_initial_timers;
     }
+    # but if contract_end was explicitly specified, that overrides all else
+    $self->set('contract_end', $opt->{'contract_end'})
+      if $opt->{'contract_end'};
     $error = $self->replace;
     if ( $error ) {
       $dbh->rollback if $oldAutoCommit;
@@ -2051,6 +2099,9 @@ sub change {
                     start_date contract_end)) {
     $hash{$date} = $self->getfield($date);
   }
+  # but if contract_end was explicitly specified, that overrides all else
+  $hash{'contract_end'} = $opt->{'contract_end'}
+    if $opt->{'contract_end'};
 
   # allow $opt->{'locationnum'} = '' to specifically set it to null
   # (i.e. customer default location)
@@ -2339,8 +2390,10 @@ The date for the package change.  Required, and must be in the future.
 
 =item quantity
 
-The pkgpart. locationnum, and quantity of the new package, with the same 
-meaning as in C<change>.
+=item contract_end
+
+The pkgpart, locationnum, quantity and optional contract_end of the new 
+package, with the same meaning as in C<change>.
 
 =back
 
@@ -2350,6 +2403,10 @@ sub change_later {
   my $self = shift;
   my $opt = ref($_[0]) ? shift : { @_ };
 
+  # check contract_end, prevent adding/removing
+  my $error = $self->_check_change($opt);
+  return $error if $error;
+
   my $oldAutoCommit = $FS::UID::AutoCommit;
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
@@ -2363,8 +2420,6 @@ sub change_later {
     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'}
@@ -2373,7 +2428,9 @@ sub change_later {
         if $opt->{'locationnum'} and $opt->{'locationnum'} != $change_to->locationnum;
     my $new_quantity = $opt->{'quantity'}
         if $opt->{'quantity'} and $opt->{'quantity'} != $change_to->quantity;
-    if ( $new_pkgpart or $new_locationnum or $new_quantity ) {
+    my $new_contract_end = $opt->{'contract_end'}
+        if $opt->{'contract_end'} and $opt->{'contract_end'} != $change_to->contract_end;
+    if ( $new_pkgpart or $new_locationnum or $new_quantity or $new_contract_end ) {
       # 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...
@@ -2413,8 +2470,10 @@ sub change_later {
       if $opt->{'locationnum'} and $opt->{'locationnum'} != $self->locationnum;
   my $new_quantity = $opt->{'quantity'}
       if $opt->{'quantity'} and $opt->{'quantity'} != $self->quantity;
+  my $new_contract_end = $opt->{'contract_end'}
+      if $opt->{'contract_end'} and $opt->{'contract_end'} != $self->contract_end;
 
-  return '' unless $new_pkgpart or $new_locationnum or $new_quantity; # wouldn't do anything
+  return '' unless $new_pkgpart or $new_locationnum or $new_quantity or $new_contract_end; # wouldn't do anything
 
   # allow $opt->{'locationnum'} = '' to specifically set it to null
   # (i.e. customer default location)
@@ -2425,7 +2484,7 @@ sub change_later {
     locationnum => $opt->{'locationnum'},
     start_date  => $date,
     map   {  $_ => ( $opt->{$_} || $self->$_() )  }
-      qw( pkgpart quantity refnum salesnum )
+      qw( pkgpart quantity refnum salesnum contract_end )
   } );
   $error = $new->insert('change' => 1, 
                         'allow_pkgpart' => ($new_pkgpart ? 0 : 1));
index 96175e1..c066ff5 100644 (file)
@@ -41,34 +41,49 @@ if ( $cgi->param('locationnum') == -1 ) {
 }
 
 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.";
+my $contract_end;
+my $now = time;
+if (defined($cgi->param('contract_end'))) {
+  $contract_end = parse_datetime($cgi->param('contract_end'));
+  if ($contract_end < $now) {
+    $error = "Contract end ".$cgi->param('contract_end')." is in the past.";
   } else {
-    # schedule the change
-    $change{'start_date'} = $date;
-    $error = $cust_pkg->change_later(\%change);
+    $change{'contract_end'} = $contract_end;
   }
-} 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'} ) {
-
-      %change = ( 'cust_pkg' => $change_to );
+}
 
+unless ($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 < $now ) {
+      $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'} and
+        $change_to->quantity     == $change{'quantity'} and
+        $change_to->contract_end == $change{'contract_end'}
+      ) {
+        %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;
+    # do a package change right now
+    my $pkg_or_error = $cust_pkg->change( \%change );
+    $error = ref($pkg_or_error) ? '' : $pkg_or_error;
+  }
 }
 
 </%init>
index 1b4a94e..e74747e 100755 (executable)
                'curr_value' => $cust_pkg->quantity
   &>
 
+% if ($use_contract_end) {
+  <& /elements/tr-input-date-field.html, {
+      'name'  => 'contract_end',
+      'value' => ($cgi->param('contract_end') || $cust_pkg->get('contract_end')),
+      'label' => '<B>Contract End</B>',
+    } &>
+% }
+
 </TABLE>
 <BR>
 
@@ -124,6 +132,8 @@ my $part_pkg = $cust_pkg->part_pkg;
 
 my $title = "Change Package";
 
+my $use_contract_end = $cust_pkg->get('contract_end') ? 1 : 0;
+
 # 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);
@@ -131,6 +141,9 @@ if ( $cust_pkg->change_to_pkgnum ) {
   foreach(qw( start_date pkgpart locationnum quantity )) {
     $cgi->param($_, $change_to->get($_));
   }
+  if ($use_contract_end) {
+    $cgi->param('contract_end', $change_to->get('contract_end'));
+  }
   $title = "Edit Scheduled Package Change";
 }
 </%init>
index 8057d8c..81156c9 100644 (file)
@@ -417,6 +417,10 @@ sub pkg_status_row_expire {
     } elsif ( $cust_pkg->change_to_pkg->locationnum != $cust_pkg->locationnum )
     {
       $title = mt('Will <b>change location</b> on');
+    } elsif (( $cust_pkg->change_to_pkg->quantity != $cust_pkg->quantity ) ||
+             ( $cust_pkg->change_to_pkg->contract_end != $cust_pkg->contract_end ))
+    {
+      $title = mt('Will change on');
     } else {
       # FS::cust_pkg->change_later should have prevented this, but 
       # just so that we can display _something_