[freeside-commits] branch master updated. f641486e28214ad1eca18c47d2252701b83614f1

Mark Wells mark at 420.am
Fri Oct 16 15:52:05 PDT 2015


The branch, master has been updated
       via  f641486e28214ad1eca18c47d2252701b83614f1 (commit)
      from  b6f16a22bd93ec66ffbb1da30e63f7e950b3b819 (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 f641486e28214ad1eca18c47d2252701b83614f1
Author: Mark Wells <mark at freeside.biz>
Date:   Fri Oct 16 15:32:32 2015 -0700

    separate setup and recur discounts, #14092

diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 479ab10..ceb347d 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -2906,6 +2906,7 @@ sub tables_hashref {
         'otaker',        'varchar', 'NULL',    32, '', '', 
         'usernum',           'int', 'NULL',    '', '', '',
         'disabled',         'char', 'NULL',     1, '', '', 
+        'setuprecur',       'char', 'NULL',     5, '', '',
       ],
       'primary_key'  => 'pkgdiscountnum',
       'unique'       => [],
diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm
index 1a3217c..ffaef97 100644
--- a/FS/FS/Template_Mixin.pm
+++ b/FS/FS/Template_Mixin.pm
@@ -3050,6 +3050,9 @@ sub _items_cust_bill_pkg {
     # if the current line item is waiting to go out, and the one we're about
     # to start is not bundled, then push out the current one and start a new
     # one.
+    if ( $d ) {
+      $d->{amount} = $d->{setup_amount} + $d->{recur_amount};
+    }
     foreach ( $s, $r, ($opt{skip_usage} ? () : $u ), $d ) {
       if ( $_ && !$cust_bill_pkg->hidden ) {
         $_->{amount}      = sprintf( "%.2f", $_->{amount} );
@@ -3485,7 +3488,8 @@ sub _items_cust_bill_pkg {
           # $item_discount->{amount} is negative
 
           if ( $d and $cust_bill_pkg->hidden ) {
-            $d->{amount}      += $item_discount->{amount};
+            $d->{setup_amount} += $item_discount->{setup_amount};
+            $d->{recur_amount} += $item_discount->{recur_amount};
           } else {
             $d = $item_discount;
             $_ = &{$escape_function}($_) foreach @{ $d->{ext_description} };
@@ -3493,27 +3497,9 @@ sub _items_cust_bill_pkg {
 
           # update the active line (before the discount) to show the 
           # original price (whether this is a hidden line or not)
-          #
-          # quotation discounts keep track of setup and recur; invoice 
-          # discounts currently don't
-          if ( exists $item_discount->{setup_amount} ) {
-
-            $s->{amount} -= $item_discount->{setup_amount} if $s;
-            $r->{amount} -= $item_discount->{recur_amount} if $r;
 
-          } else {
-
-            # $active_line is the line item hashref for the line that will
-            # show the original price
-            # (use the recur or single line for the package, unless we're 
-            # showing a setup line for a package with no recurring fee)
-            my $active_line = $r;
-            if ( $type eq 'S' ) {
-              $active_line = $s;
-            }
-            $active_line->{amount} -= $item_discount->{amount};
-
-          }
+          $s->{amount} -= $item_discount->{setup_amount} if $s;
+          $r->{amount} -= $item_discount->{recur_amount} if $r;
 
         } # if there are any discounts
       } # if this is an appropriate place to show discounts
@@ -3522,6 +3508,11 @@ sub _items_cust_bill_pkg {
 
   }
 
+  # discount amount is internally split up
+  if ( $d ) {
+    $d->{amount} = $d->{setup_amount} + $d->{recur_amount};
+  }
+
   foreach ( $s, $r, ($opt{skip_usage} ? () : $u ), $d ) {
     if ( $_  ) {
       $_->{amount}      = sprintf( "%.2f", $_->{amount} ),
diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm
index 1780426..5861ee4 100644
--- a/FS/FS/cust_bill_pkg.pm
+++ b/FS/FS/cust_bill_pkg.pm
@@ -820,6 +820,8 @@ quantity.
 
 sub _item_discount {
   my $self = shift;
+  my %options = @_;
+
   my @pkg_discounts = $self->pkg_discount;
   return if @pkg_discounts == 0;
   # special case: if there are old "discount details" on this line item, don't
@@ -832,7 +834,8 @@ sub _item_discount {
   my $d = {
     _is_discount    => 1,
     description     => $self->mt('Discount'),
-    amount          => 0,
+    setup_amount    => 0,
+    recur_amount    => 0,
     ext_description => \@ext,
     pkgpart         => $self->pkgpart,
     feepart         => $self->feepart,
@@ -840,9 +843,11 @@ sub _item_discount {
   };
   foreach my $pkg_discount (@pkg_discounts) {
     push @ext, $pkg_discount->description;
-    $d->{amount} -= $pkg_discount->amount;
+    my $setuprecur = $pkg_discount->cust_pkg_discount->setuprecur;
+    $d->{$setuprecur.'_amount'} -= $pkg_discount->amount;
   } 
-  $d->{amount} *= $self->quantity || 1;
+  $d->{setup_amount} *= $self->quantity || 1; # ??
+  $d->{recur_amount} *= $self->quantity || 1; # ??
   
   return $d;
 }
diff --git a/FS/FS/cust_bill_pkg_discount.pm b/FS/FS/cust_bill_pkg_discount.pm
index 9e64d20..616657a 100644
--- a/FS/FS/cust_bill_pkg_discount.pm
+++ b/FS/FS/cust_bill_pkg_discount.pm
@@ -135,10 +135,36 @@ Returns a string describing the discount (for use on an invoice).
 sub description {
   my $self = shift;
   my $discount = $self->cust_pkg_discount->discount;
+
+  if ( $self->months == 0 ) {
+    # then this is a setup discount
+    my $desc = $discount->name;
+    if ( $desc ) {
+      $desc .= ': ';
+    } else {
+      $desc = $self->mt('Setup discount of ');
+    }
+    if ( (my $percent = $discount->percent) > 0 ) {
+      $percent = sprintf('%.1f', $percent) if $percent > int($percent);
+      $percent =~ s/\.0+$//;
+      $desc .= $percent . '%';
+    } else {
+      # note "$self->amount", not $discount->amount. if a flat discount
+      # is applied to the setup fee, show the amount actually discounted.
+      # we might do this for all types of discounts.
+      my $money_char = FS::Conf->new->config('money_char') || '$';
+      $desc .= $money_char . sprintf('%.2f', $self->amount);
+    }
+  
+    # don't show "/month", months remaining or used, etc., as for setup
+    # discounts it doesn't matter.
+    return $desc;
+  }
+
   my $desc = $discount->description_short;
   $desc .= $self->mt(' each') if $self->cust_bill_pkg->quantity > 1;
 
-  if ($discount->months) {
+  if ( $discount->months and $self->months > 0 ) {
     # calculate months remaining on this cust_pkg_discount after this invoice
     my $date = $self->cust_bill_pkg->cust_bill->_date;
     my $used = FS::Record->scalar_sql(
@@ -152,7 +178,7 @@ sub description {
     $used ||= 0;
     my $remaining = sprintf('%.2f', $discount->months - $used);
     $desc .= $self->mt(' for [quant,_1,month] ([quant,_2,month] remaining)',
-              $self->months,
+              sprintf('%.2f', $self->months),
               $remaining
              );
   }
diff --git a/FS/FS/cust_main/Packages.pm b/FS/FS/cust_main/Packages.pm
index c147e55..ead97f2 100644
--- a/FS/FS/cust_main/Packages.pm
+++ b/FS/FS/cust_main/Packages.pm
@@ -197,7 +197,7 @@ sub order_pkg {
         map { $_ => $cust_pkg->$_() }
           qw( pkgbatch
               start_date order_date expire adjourn contract_end
-              refnum discountnum waive_setup
+              refnum setup_discountnum recur_discountnum waive_setup
             )
     });
     $error = $self->order_pkg('cust_pkg'    => $pkg,
diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
index 279205b..d741907 100644
--- a/FS/FS/cust_pkg.pm
+++ b/FS/FS/cust_pkg.pm
@@ -425,7 +425,7 @@ sub insert {
     }
   }
 
-  if ( $self->discountnum ) {
+  if ( $self->setup_discountnum || $self->recur_discountnum ) {
     my $error = $self->insert_discount();
     if ( $error ) {
       $dbh->rollback if $oldAutoCommit;
@@ -4318,13 +4318,10 @@ sub insert_reason {
 Associates this package with a discount (see L<FS::cust_pkg_discount>, possibly
 inserting a new discount on the fly (see L<FS::discount>).
 
-Available options are:
-
-=over 4
-
-=item discountnum
-
-=back
+This will look at the cust_pkg for a pseudo-field named "setup_discountnum",
+and if present, will create a setup discount. If the discountnum is -1,
+a new discount definition will be inserted using the value in
+"setup_discountnum_amount" or "setup_discountnum_percent". Likewise for recur.
 
 If there is an error, returns the error, otherwise returns false.
 
@@ -4334,21 +4331,29 @@ sub insert_discount {
   #my ($self, %options) = @_;
   my $self = shift;
 
-  my $cust_pkg_discount = new FS::cust_pkg_discount {
-    'pkgnum'      => $self->pkgnum,
-    'discountnum' => $self->discountnum,
-    'months_used' => 0,
-    'end_date'    => '', #XXX
-    #for the create a new discount case
-    '_type'       => $self->discountnum__type,
-    'amount'      => $self->discountnum_amount,
-    'percent'     => $self->discountnum_percent,
-    'months'      => $self->discountnum_months,
-    'setup'      => $self->discountnum_setup,
-    #'disabled'    => $self->discountnum_disabled,
-  };
+  foreach my $x (qw(setup recur)) {
+    if ( my $discountnum = $self->get("${x}_discountnum") ) {
+      my $cust_pkg_discount = FS::cust_pkg_discount->new( {
+        'pkgnum'      => $self->pkgnum,
+        'discountnum' => $discountnum,
+        'setuprecur'  => $x,
+        'months_used' => 0,
+        'end_date'    => '', #XXX
+        #for the create a new discount case
+        'amount'      => $self->get("${x}_discountnum_amount"),
+        'percent'     => $self->get("${x}_discountnum_percent"),
+        'months'      => $self->get("${x}_discountnum_months"),
+      } );
+      if ( $x eq 'setup' ) {
+        $cust_pkg_discount->setup('Y');
+        $cust_pkg_discount->months('');
+      }
+      my $error = $cust_pkg_discount->insert;
+      return $error if $error;
+    }
+  }
 
-  $cust_pkg_discount->insert;
+  '';
 }
 
 =item set_usage USAGE_VALUE_HASHREF 
diff --git a/FS/FS/cust_pkg_discount.pm b/FS/FS/cust_pkg_discount.pm
index 5d0f85b..aa89816 100644
--- a/FS/FS/cust_pkg_discount.pm
+++ b/FS/FS/cust_pkg_discount.pm
@@ -59,6 +59,9 @@ end_date
 
 order taker, see L<FS::access_user>
 
+=item setuprecur
+
+whether this discount applies to setup fees or recurring fees
 
 =back
 
@@ -125,11 +128,29 @@ sub check {
     || $self->ut_alphan('otaker')
     || $self->ut_numbern('usernum')
     || $self->ut_enum('disabled', [ '', 'Y' ] )
+    || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] )
   ;
   return $error if $error;
 
-  return "Discount does not apply to setup fees, and package has no recurring"
-    if ! $self->discount->setup && $self->cust_pkg->part_pkg->freq =~ /^0/;
+  my $cust_pkg = $self->cust_pkg;
+  my $discount = $self->discount;
+  if ( $self->setuprecur eq 'setup' ) {
+    if ( !$discount->setup ) {
+      # UI prevents this, and historical discounts should never have it either
+      return "Discount #".$self->discountnum." can't be applied to setup fees.";
+    } elsif ( $cust_pkg->base_setup == 0 ) {
+      # and this
+      return "Can't apply setup discount to a package with no setup fee.";
+    }
+    # else we're good. do NOT disallow applying setup discounts when the
+    # setup date is already set; upgrades use that.
+  } else {
+    if ( $self->cust_pkg->base_recur == 0 ) {
+      return "Can't apply recur discount to a package with no recurring fee.";
+    } elsif ( $cust_pkg->part_pkg->freq eq '0' ) {
+      return "Can't apply recur discount to a one-time charge.";
+    }
+  }
 
   $self->usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->usernum;
 
@@ -205,6 +226,45 @@ sub status {
 sub _upgrade_data {  # class method
   my ($class, %opts) = @_;
   $class->_upgrade_otaker(%opts);
+
+  # #14092: set setuprecur field on discounts. if we get one that applies to
+  # both setup and recur, split it into two discounts.
+  my $search = FS::Cursor->new({
+      table   => 'cust_pkg_discount',
+      hashref => { setuprecur => '' }
+  });
+  while ( my $cust_pkg_discount = $search->fetch ) {
+    my $discount = $cust_pkg_discount->discount;
+    my $cust_pkg = $cust_pkg_discount->cust_pkg;
+    # 1. Does it apply to the setup fee?
+    # Yes, if: the discount applies to setup fees generally, and the package
+    # has a setup fee.
+    # No, if: the discount is a flat amount, and is not first-month only.
+    if ( $discount->setup
+        and $cust_pkg->base_setup > 0
+        and ($discount->amount == 0 or $discount->months == 1)
+       )
+    {
+      # then clone this discount into a new one
+      my $setup_discount = FS::cust_pkg_discount->new({
+          $cust_pkg_discount->hash,
+          setuprecur      => 'setup',
+          pkgdiscountnum  => ''
+      });
+      my $error = $setup_discount->insert;
+      die "$error (migrating cust_pkg_discount to setup discount)" if $error;
+    }
+    # 2. Does it apply to the recur fee?
+    # Yes, if: the package has a recur fee.
+    if ( $cust_pkg->base_recur > 0 ) {
+      # then modify this discount in place
+      $cust_pkg_discount->set('setuprecur' => 'recur');
+      my $error = $cust_pkg_discount->replace;
+      die "$error (migrating cust_pkg_discount)" if $error;
+    }
+    # not in here yet: splitting the cust_bill_pkg_discount records.
+    # (not really necessary)
+  }
 }
 
 =back
diff --git a/FS/FS/discount.pm b/FS/FS/discount.pm
index e113357..13146a9 100644
--- a/FS/FS/discount.pm
+++ b/FS/FS/discount.pm
@@ -119,12 +119,12 @@ sub check {
 
   if ( $self->_type eq 'Select discount type' ) {
     return 'Please select a discount type';
-  } elsif ( $self->_type eq 'Amount' ) {
-    $self->percent('0');
-    return 'Amount must be greater than 0' unless $self->amount > 0;
-  } elsif ( $self->_type eq 'Percentage' ) {
-    $self->amount('0.00');
-    return 'Percentage must be greater than 0' unless $self->percent > 0;
+  } elsif ( $self->amount > 0 ) {
+    $self->set('percent', '0');
+  } elsif ( $self->percent > 0 ) {
+    $self->set('amount', '0.00');
+  } else {
+    return "Discount amount or percentage must be > 0";
   }
 
   my $error = 
diff --git a/FS/FS/part_pkg/discount_Mixin.pm b/FS/FS/part_pkg/discount_Mixin.pm
index 5de7d8e..1e39f6a 100644
--- a/FS/FS/part_pkg/discount_Mixin.pm
+++ b/FS/FS/part_pkg/discount_Mixin.pm
@@ -50,6 +50,9 @@ sub calc_discount {
   my $tot_discount = 0;
   #UI enforces just 1 for now, will need ordering when they can be stacked
 
+  # discount setup/recur splitting DOES NOT TOUCH THIS YET.
+  # we need some kind of monitoring to see who if anyone still uses term
+  # discounts.
   if ( $param->{freq_override} ) {
     # When a customer pays for more than one month at a time to receive a 
     # term discount, freq_override is set to the number of months.
@@ -80,6 +83,13 @@ sub calc_discount {
   }
 
   my @cust_pkg_discount = $cust_pkg->cust_pkg_discount_active;
+
+  if ( defined $param->{'setup_charge'} ) {
+    @cust_pkg_discount = grep { $_->setuprecur eq 'setup' } @cust_pkg_discount;
+  } else {
+    @cust_pkg_discount = grep { $_->setuprecur eq 'recur' } @cust_pkg_discount;
+  }
+    
   foreach my $cust_pkg_discount ( @cust_pkg_discount ) {
     my $discount_left;
     my $discount = $cust_pkg_discount->discount;
@@ -115,23 +125,17 @@ sub calc_discount {
         # if it's a flat amount discount for other than one month:
         # - skip the discount. unsure, leaving it alone for now.
 
-        next unless $discount->setup;
-
         $months = 0; # never count a setup discount as a month of discount
                      # (the recur discount in the same month should do it)
 
         if ( $discount->percent > 0 ) {
             $amount = $discount->percent * $param->{'setup_charge'} / 100;
-        } elsif ( $discount->amount > 0 && ($discount->months || 0) == 1) {
+        } elsif ( $discount->amount > 0 ) {
             # apply the discount amount, up to a maximum of the setup charge
             $amount = min($discount->amount, $param->{'setup_charge'});
             $discount_left = sprintf('%.2f', $discount->amount - $amount);
             # transfer remainder of discount, if any, to recur
             $param->{'discount_left_recur'}{$discount->discountnum} = $discount_left;
-        } else {
-          # I guess we don't allow multiple-month flat amount discounts to
-          # apply to setup?
-            next; 
         }
 
     } else {
@@ -180,20 +184,27 @@ sub calc_discount {
       #    recur discount is zero. 
       #}
 
-      # transfer remainder of discount, if any, to setup
-      # this is used when the recur phase wants to add a setup fee
+      # Transfer remainder of discount, if any, to setup
+      # This is used when the recur phase wants to add a setup fee
       # (prorate_defer_bill): the "discount_left_setup" amount will
-      # be subtracted in _make_lines.
-      if ( $discount->setup && $discount->amount > 0
-          && ($discount->months || 0) != 1
-         )
+      # be subtracted in _make_lines. 
+      if ( $discount->amount > 0 && ($discount->months || 0) != 1 )
       {
-        # $amount is no longer permonth at this point! correct. very good.
-        $discount_left = $amount - $recur_charge; # backward, as above
-        if ( $discount_left > 0 ) {
-          $amount = $recur_charge;
-          $param->{'discount_left_setup'}{$discount->discountnum} = 
-            0 - $discount_left;
+        # make sure there is a setup discount with this discountnum
+        # on the same package.
+        if ( qsearchs('cust_pkg_discount', {
+              pkgnum      => $cust_pkg->pkgnum,
+              discountnum => $discount->discountnum,
+              setuprecur  => 'setup'
+            }) )
+        {
+          # $amount is no longer permonth at this point! correct. very good.
+          $discount_left = $amount - $recur_charge; # backward, as above
+          if ( $discount_left > 0 ) {
+            $amount = $recur_charge;
+            $param->{'discount_left_setup'}{$discount->discountnum} = 
+              0 - $discount_left;
+          }
         }
       }
 
@@ -210,7 +221,7 @@ sub calc_discount {
         };
       }
 
-    }
+    } # else not 'setup_charge'
 
     $amount = sprintf('%.2f', $amount + 0.00000001 ); #so 1.005 rounds to 1.01
 
@@ -221,7 +232,7 @@ sub calc_discount {
       'pkgdiscountnum' => $cust_pkg_discount->pkgdiscountnum,
       'amount'         => $amount,
       'months'         => $months,
-      # XXX should have a 'setuprecur'
+      # 'setuprecur' is implied by the cust_pkg_discount link
     };
     push @{ $param->{'discounts'} }, $cust_bill_pkg_discount;
     $tot_discount += $amount;
diff --git a/httemplate/edit/cust_pkg_discount.html b/httemplate/edit/cust_pkg_discount.html
index 0bb84b8..e1e3dae 100755
--- a/httemplate/edit/cust_pkg_discount.html
+++ b/httemplate/edit/cust_pkg_discount.html
@@ -1,18 +1,5 @@
-<% include('/elements/header-popup.html', "Discount Package") %>
-
-<SCRIPT TYPE="text/javascript">
-
-  function enable_discount_pkg () {
-    if ( document.DiscountPkgForm.discountnum.selectedIndex > 0 ) {
-      document.DiscountPkgForm.submit.disabled = false;
-    } else {
-      document.DiscountPkgForm.submit.disabled = true;
-    }
-  }
-
-</SCRIPT>
-
-<% include('/elements/error.html') %>
+<& /elements/header-popup.html, "Discount Package" &>
+<& /elements/error.html &>
 
 <FORM NAME="DiscountPkgForm" ACTION="<% $p %>edit/process/cust_pkg_discount.html" METHOD=POST>
 <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
@@ -26,17 +13,17 @@
     </TD>
   </TR>
 
-<% include('/elements/tr-select-discount.html',
-             'empty_label' => ( $pkgdiscountnum ? '' : 'Select discount' ),
-             'onchange'    => 'enable_discount_pkg()',
-             'cgi'         => $cgi,
-          )
-%>
-
+<& /elements/tr-select-pkg-discount.html,
+  curr_value_setup  => $setup_discountnum,
+  curr_value_recur  => $recur_discountnum,
+  disable_setup     => $disable_setup,
+  disable_recur     => $disable_recur,
+&>
+  
 </TABLE>
 
 <BR>
-<INPUT NAME="submit" TYPE="submit" VALUE="Discount package" <% $pkgdiscountnum ? '' : 'DISABLED' %>>
+<INPUT NAME="submit" TYPE="submit" VALUE="Discount package">
 
 </FORM>
 </BODY>
@@ -44,14 +31,13 @@
 
 <%init>
 
-#some false laziness w/misc/change_pkg.cgi
-
 my $conf = new FS::Conf;
 
 my $curuser = $FS::CurrentUser::CurrentUser;
 
 die "access denied"
-  unless $curuser->access_right('Discount customer package');
+  unless $curuser->access_right([ 'Discount customer package',
+                                  'Waive setup fee']);
 
 my $pkgnum = scalar($cgi->param('pkgnum'));
 $pkgnum =~ /^(\d+)$/ or die "illegal pkgnum $pkgnum";
@@ -67,10 +53,30 @@ my $cust_pkg =
     'extra_sql' => ' AND '. $curuser->agentnums_sql,
   }) or die "unknown pkgnum $pkgnum";
 
-#my $cust_main = $cust_pkg->cust_main
-#  or die "can't get cust_main record for custnum ". $cust_pkg->custnum.
-#         " ( pkgnum ". cust_pkg->pkgnum. ")";
-
 my $part_pkg = $cust_pkg->part_pkg;
 
+my @discounts = $cust_pkg->cust_pkg_discount_active;
+my ($setup_discountnum, $recur_discountnum);
+foreach (@discounts) {
+  if ( $_->setuprecur eq 'setup') {
+    die "multiple setup discounts on pkg#$pkgnum" if $setup_discountnum;
+    $setup_discountnum = $_->discountnum;
+  } elsif ( $_->setuprecur eq 'recur' ) {
+    die "multiple setup discounts on pkg#$pkgnum" if $recur_discountnum;
+    $recur_discountnum = $_->discountnum;
+  }
+}
+if ( $cust_pkg->waive_setup ) {
+  $setup_discountnum = -2;
+}
+
+my $disable_setup = 1;
+if ( !$cust_pkg->get('setup') and $cust_pkg->base_setup > 0 ) {
+  $disable_setup = 0;
+}
+my $disable_recur = 1;
+if ( $cust_pkg->base_recur > 0 ) {
+  $disable_recur = 0;
+}
+
 </%init>
diff --git a/httemplate/edit/process/cust_pkg_discount.html b/httemplate/edit/process/cust_pkg_discount.html
index 4a71f69..143611e 100644
--- a/httemplate/edit/process/cust_pkg_discount.html
+++ b/httemplate/edit/process/cust_pkg_discount.html
@@ -14,9 +14,8 @@
 <%init>
 
 my $curuser = $FS::CurrentUser::CurrentUser;
-
-die "access denied"
-  unless $curuser->access_right('Discount customer package');
+my $can_discount = $curuser->access_right('Discount customer package');
+my $can_waive_setup = $curuser->access_right('Waive setup fee');
 
 #this search is really for security wrt agent virt...
 #maybe move it to the cust_pkg_discount->insert call?
@@ -29,20 +28,81 @@ my $cust_pkg = qsearchs({
 });
 die 'unknown pkgnum' unless $cust_pkg;
 
-my $cust_pkg_discount = new FS::cust_pkg_discount {
-  'pkgnum'      => $cust_pkg->pkgnum,
-  'discountnum' => scalar($cgi->param('discountnum')),
-  'months_used' => 0,
-  'end_date'    => '', #XXX
-  #for the create a new discount case
-  '_type'       => scalar($cgi->param('discountnum__type')),
-  'amount'      => scalar($cgi->param('discountnum_amount')),
-  'percent'     => scalar($cgi->param('discountnum_percent')),
-  'months'      => scalar($cgi->param('discountnum_months')),
-  'setup'       => scalar($cgi->param('discountnum_setup')),
-  #'linked'       => scalar($cgi->param('discountnum_linked')),
-  #'disabled'    => $self->discountnum_disabled,
-};
-my $error = $cust_pkg_discount->insert;
+my $error;
+my %discountnum = (setup => '', recur => '');
+if ( $cgi->param('setup_discountnum') == -2 ) {
+
+  die "access denied" unless $can_waive_setup; # UI protects against this
+  # waive setup fee (not really a discount but treated as one in the UI)
+  if ( !$cust_pkg->get('setup') and !$cust_pkg->waive_setup ) {
+    $cust_pkg->set('waive_setup' => 'Y');
+    $error = $cust_pkg->replace;
+  }
+
+} else {
+  if ( $cgi->param('setup_discountnum') =~ /^(-?\d+)$/ ) {
+    $discountnum{setup} = $1;
+  }
+  if ( $cust_pkg->waive_setup ) {
+    $cust_pkg->set('waive_setup', '');
+    $error = $cust_pkg->replace;
+  }
+}
+
+if ( $cgi->param('recur_discountnum') =~ /^(-?\d+)$/ ) {
+
+  $discountnum{recur} = $1;
+
+}
+
+my @active_discounts = $cust_pkg->cust_pkg_discount_active;
+
+foreach my $setuprecur (qw(setup recur)) {
+
+  if ( $cust_pkg->get('setup') and $setuprecur eq 'setup' ) {
+    # no point allowing setup discounts to be edited for a previously setup
+    # package
+    next;
+  }
+
+  my ($active) = grep { $_->setuprecur eq $setuprecur } @active_discounts;
+
+  if ( $active ) {
+    if ( $active->discount ne $discountnum{$setuprecur} ) {
+      $active->set('disabled' => 'Y');
+      $error ||= $active->replace;
+      undef $active;
+    } else {
+      # it's the same discountnum; don't touch it
+      next;
+    }
+  }
+
+  if ( $discountnum{$setuprecur} ) {
+    die "access_denied" unless $can_discount;
+    my $cust_pkg_discount = FS::cust_pkg_discount->new({
+      'pkgnum'      => $cust_pkg->pkgnum,
+      'discountnum' => $discountnum{$setuprecur},
+      'setuprecur'  => $setuprecur,
+      'months_used' => 0,
+      'end_date'    => '', #XXX
+      #for the create a new discount case
+      '_type'       => scalar($cgi->param($setuprecur.'_discountnum__type')),
+      'amount'      => scalar($cgi->param($setuprecur.'_discountnum_amount')),
+      'percent'     => scalar($cgi->param($setuprecur.'_discountnum_percent')),
+    });
+    if ( $setuprecur eq 'setup' ) {
+      $cust_pkg_discount->set('setup' => 'Y');
+      $cust_pkg_discount->set('months' => 1);
+    } else {
+      if ( $cgi->param($setuprecur.'_discountnum_months') =~ /^(\w+)$/ ) {
+        $cust_pkg_discount->set('months' => $1);
+      }
+    }
+
+    $error ||= $cust_pkg_discount->insert;
+
+  }
+} # foreach $setuprecur
 
 </%init>
diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi
index f1d8c26..6035215 100644
--- a/httemplate/edit/process/quick-cust_pkg.cgi
+++ b/httemplate/edit/process/quick-cust_pkg.cgi
@@ -79,9 +79,6 @@ my $contactnum = $1;
 $cgi->param('locationnum') =~ /^(\-?\d*)$/
   or die 'illegal locationnum '. $cgi->param('locationnum');
 my $locationnum = $1;
-$cgi->param('discountnum') =~ /^(\-?\d*)$/
-  or die 'illegal discountnum '. $cgi->param('discountnum');
-my $discountnum = $1;
 
 # for going right to a provision service after ordering a package
 my( $svcpart, $part_svc ) = ( '', '' );
@@ -114,19 +111,29 @@ my %hash = (
     'refnum'               => $refnum,
     'contactnum'           => $contactnum,
     'locationnum'          => $locationnum,
-    'discountnum'          => $discountnum,
-    #for the create a new discount case
-    'discountnum__type'    => scalar($cgi->param('discountnum__type')),
-    'discountnum_amount'   => scalar($cgi->param('discountnum_amount')),
-    'discountnum_percent'  => scalar($cgi->param('discountnum_percent')),
-    'discountnum_months'   => scalar($cgi->param('discountnum_months')),
-    'discountnum_setup'    => scalar($cgi->param('discountnum_setup')),
     'contract_end'         => ( scalar($cgi->param('contract_end'))
                                   ? parse_datetime($cgi->param('contract_end'))
                                   : ''
                               ),
-     'waive_setup'         => ( $cgi->param('waive_setup') eq 'Y' ? 'Y' : '' ),
 );
+
+if ( $cgi->param('setup_discountnum') =~ /^(-?\d+)$/ ) { 
+  if ( $1 == -2 ) {
+    $hash{waive_setup} = 'Y';
+  } else {
+    $hash{setup_discountnum} = $1;
+    $hash{setup_discountnum_amount} = $cgi->param('setup_discountnum_amount');
+    $hash{setup_discountnum_percent} = $cgi->param('setup_discountnum_percent');
+  }
+}
+
+if ( $cgi->param('recur_discountnum') =~ /^(-?\d+)$/ ) { 
+  $hash{recur_discountnum} = $1;
+  $hash{recur_discountnum_amount} = $cgi->param('recur_discountnum_amount');
+  $hash{recur_discountnum_percent} = $cgi->param('recur_discountnum_percent');
+  $hash{recur_discountnum_months} = $cgi->param('recur_discountnum_months');
+}
+
 $hash{'custnum'} = $cust_main->custnum if $cust_main;
 
 if ( $cgi->param('start') eq 'on_hold' ) {
diff --git a/httemplate/elements/tr-select-months.html b/httemplate/elements/select-months.html
similarity index 90%
copy from httemplate/elements/tr-select-months.html
copy to httemplate/elements/select-months.html
index b90ce1e..1cd72fc 100644
--- a/httemplate/elements/tr-select-months.html
+++ b/httemplate/elements/select-months.html
@@ -8,4 +8,4 @@ $opt{labels} = { '' => '',
                };
 
 </%init>
-<& tr-select.html, %opt &>
+<& select.html, %opt &>
diff --git a/httemplate/elements/tr-select-months.html b/httemplate/elements/tr-select-months.html
index b90ce1e..4d85764 100644
--- a/httemplate/elements/tr-select-months.html
+++ b/httemplate/elements/tr-select-months.html
@@ -1,11 +1,5 @@
-<%init>
-my %opt = @_;
-$opt{id} ||= $opt{field}; # should be the default everywhere
-my $max = $opt{max} || 36;
-$opt{options} = [ '', 1 .. $max ];
-$opt{labels} = { '' => '',
-                 map { $_ => emt('[quant,_1,month]', $_) } 1 .. $max
-               };
-
-</%init>
-<& tr-select.html, %opt &>
+<& tr-td-label.html, @_ &>
+  <td>
+    <& select-months.html, @_ &>
+  </td>
+</tr>
diff --git a/httemplate/elements/tr-select-pkg-discount.html b/httemplate/elements/tr-select-pkg-discount.html
new file mode 100644
index 0000000..dc38cff
--- /dev/null
+++ b/httemplate/elements/tr-select-pkg-discount.html
@@ -0,0 +1,196 @@
+<%doc>
+
+In order_pkg.html or similar:
+
+<& /elements/tr-select-pkg-discount.html,
+  curr_value_setup => ($cgi->param('setup_discountnum') || ''),
+  curr_value_recur => ($cgi->param('recur_discountnum') || ''),
+  disable_setup    => 0,
+  disable_recur    => 0,
+&>
+
+This provides the following:
+- If the user can waive setup fees or apply a discount, they get a 
+  select box for the setup discount, with "Waive setup fee" as an option.
+- If they can custom discount, they will also get "Custom discount" as an
+  option. If selected, this will show fields to enter the custom discount
+  amount/percentage.
+- If they can waive setup fees but NOT apply a discount, they only get a
+  checkbox to waive setup fee.
+- Same for recurring fee, but without the "waive setup fee" stuff, obviously.
+- Custom recurring discounts also have an option for a duration in months.
+
+"disable_setup" locks the setup discount, but will still show a static
+description if curr_value_setup is set. Likewise "disable_recur".
+
+</%doc>
+% # SETUP DISCOUNT
+
+% # select-discount knows about the "custom discount" ACL
+% if ( $curuser->access_right('Discount customer package')
+%      and !$opt{disable_setup} )
+% {
+%   my $pre_options = [ '' => '(none)' ];
+%   if ( $curuser->access_right('Waive setup fee') ) {
+%     push @$pre_options, -2 => 'Waive setup fee';
+%   }
+<& tr-td-label.html, label => emt('Setup fee') &>
+  <td>
+    <& select-discount.html,
+      field       => 'setup_discountnum',
+      id          => 'setup_discountnum',
+      hashref     =>  { disabled => '',
+                        setup    => 'Y'
+                      },
+      extra_sql   =>  ' AND (percent > 0 OR months = 1)',
+      curr_value  => $opt{'curr_value_setup'},
+      disable_empty => 1,
+      pre_options => $pre_options,
+    &>
+  </td>
+</tr>
+% # custom discount
+<tr class="setup_discount_custom">
+  <td></td>
+  <td>Amount <% $money_char %>
+    <& input-text.html,
+      field       => 'setup_discountnum_amount',
+      curr_value  => ($cgi->param('setup_discountnum_amount') || ''),
+      size        => 5,
+    &>
+  or percentage
+    <& input-text.html,
+      field       => 'setup_discountnum_percent',
+      curr_value  => ($cgi->param('setup_discountnum_percent') || ''),
+      size        => 5,
+    &> %
+  </td>
+</tr>
+
+% } elsif ( $curuser->access_right('Waive setup fee')
+%           and !$opt{disable_setup} )
+% {
+
+<& tr-td-label.html, label => emt('Waive setup fee') &>
+  <td>
+  <& checkbox.html,
+      field       => 'setup_discountnum',
+      id          => 'setup_discountnum',
+      value       => '-2',
+      curr_value  => $opt{'curr_value_setup'},
+  &>
+  </td>
+</tr>
+
+% } elsif ( $opt{'curr_value_setup'} ) { # user can't do anything
+%
+%   my $discount = FS::discount->by_key($opt{'curr_value_setup'});
+
+  <INPUT TYPE="hidden" NAME="setup_discountnum" VALUE="<% $opt{curr_value_setup} %>">
+
+  <% $discount->description_short %>
+
+% }
+
+% # RECUR DISCOUNT
+
+% if ( $curuser->access_right('Discount customer package')
+%      and !$opt{disable_recur} ) {
+
+<& tr-td-label.html, label => emt('Recurring fee') &>
+  <td>
+    <& select-discount.html,
+      field       => 'recur_discountnum',
+      id          => 'recur_discountnum',
+      hashref     =>  { disabled => '' },
+      curr_value  => $opt{'curr_value_recur'},
+    &>
+
+  </td>
+</tr>
+% # custom discount
+<tr class="recur_discount_custom">
+  <td></td>
+  <td>Amount <% $money_char %>
+    <& input-text.html,
+      field       => 'recur_discountnum_amount',
+      curr_value  => ($cgi->param('recur_discountnum_amount') || ''),
+      size        => 5,
+    &>
+  or percentage
+    <& input-text.html,
+      field       => 'recur_discountnum_percent',
+      curr_value  => ($cgi->param('recur_discountnum_percent') || ''),
+      size        => 5,
+    &> %
+  </td>
+</tr>
+<tr class="recur_discount_custom">
+  <td></td>
+  <td>Expires after
+    <& /elements/select-months.html,
+      field       => 'recur_discountnum_months',
+      curr_value  => ($cgi->param('recur_discountnum_months') || ''),
+    &>
+  </td>
+</tr>
+
+% } elsif ( $opt{'curr_value_recur'} ) {
+%
+%   my $discount = FS::discount->by_key($opt{'curr_value_recur'});
+
+  <INPUT TYPE="hidden" NAME="recur_discountnum" VALUE="<% $opt{curr_value_recur} %>">
+
+  <% $discount->description %>
+
+% }
+
+<SCRIPT TYPE="text/javascript">
+$(document).ready(function() {
+  ['setup', 'recur'].forEach(function(x) {
+    var discountnum = $('#'+x+'_discountnum');
+
+    // if it's been set to a custom discount, show custom discount inputs
+    var discountnum_changed = function() {
+      var val = this.value;
+      var custom = $('.'+x+'_discount_custom');
+      if ( val == -1 ) {
+        custom.show();
+      } else {
+        custom.hide();
+      }
+    };
+
+    discountnum.on('change', discountnum_changed);
+    discountnum.trigger('change');
+
+    // if amount contains a value, disable percent, and vice versa
+    var amount_percent_keyup = function(event) {
+      var other = event.data;
+      if (this.value.length > 0) {
+        other.disabled = true;
+      } else {
+        other.disabled = false;
+      }
+    };
+    var amount = $('#'+x+'_discountnum_amount');
+    var percent = $('#'+x+'_discountnum_percent');
+    amount.on('keyup', percent, amount_percent_keyup);
+    percent.on('keyup', amount, amount_percent_keyup);
+
+    amount.trigger('keyup');
+    percent.trigger('keyup');
+  });
+});
+</script>
+<%init>
+
+my %opt = (
+  'curr_value_setup' => ($cgi->param('setup_discountnum') || ''),
+  'curr_value_recur' => ($cgi->param('recur_discountnum') || ''),
+  @_
+);
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $money_char = FS::Conf->new->config('money_char') || '$';
+
+</%init>
diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html
index cb2bd48..e282501 100644
--- a/httemplate/misc/order_pkg.html
+++ b/httemplate/misc/order_pkg.html
@@ -127,22 +127,7 @@
 % if ( $discount_cust_pkg || $waive_setup_fee ) {
   <FONT CLASS="fsinnerbox-title"><% mt('Discounting') |h %></FONT>
   <% ntable("#cccccc") %>
-
-%   if ( $waive_setup_fee ) {
-      <TR>
-        <TH ALIGN="right"><% mt('Waive setup fee') |h %> </TH>
-        <TD COLSPAN=6><INPUT TYPE="checkbox" NAME="waive_setup" VALUE="Y"></TD>
-      </TR>
-%   }
-
-%   if ( $discount_cust_pkg ) {
-      <& /elements/tr-select-discount.html,
-               'element_etc' => 'DISABLED',
-               'colspan'     => 7,
-               'cgi'         => $cgi,
-      &>
-%   }
-
+    <& /elements/tr-select-pkg-discount.html &>
   </TABLE><BR>
 
 % }
diff --git a/httemplate/search/cust_bill_pkg_discount.html b/httemplate/search/cust_bill_pkg_discount.html
index 6da5787..c634828 100644
--- a/httemplate/search/cust_bill_pkg_discount.html
+++ b/httemplate/search/cust_bill_pkg_discount.html
@@ -20,9 +20,7 @@
                    sub { $_[0]->cust_pkg_discount->discount->description },
                    sub { $_[0]->cust_pkg_discount->discount->classname },
                    sub { sprintf($money_char.'%.2f', shift->amount ) },
-                   sub { my $m = shift->months;
-                         $m =~ /\./ ? sprintf('%.2f', $m) : $m;
-                       },
+                   $months_sub,
                    'pkg',#sub { $_[0]->cust_bill_pkg->cust_pkg->part_pkg->pkg },
                    'invnum',
                    sub { time2str('%b %d %Y', shift->_date ) },
@@ -218,4 +216,11 @@ my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
 my $conf = new FS::Conf;
 my $money_char = $conf->config('money_char') || '$';
 
+my $months_sub = sub {
+  my $cust_bill_pkg_discount = shift;
+  return 'Setup'
+    if $cust_bill_pkg_discount->cust_pkg_discount->setuprecur eq 'setup';
+  sprintf('%.2f', $cust_bill_pkg_discount->months);
+};
+
 </%init>
diff --git a/httemplate/search/cust_pkg_discount.html b/httemplate/search/cust_pkg_discount.html
index f0c7447..ab6ad2b 100644
--- a/httemplate/search/cust_pkg_discount.html
+++ b/httemplate/search/cust_pkg_discount.html
@@ -18,9 +18,7 @@
                                      sub { ucfirst( shift->status ) },
                                      sub { shift->discount->description },
                                      sub { shift->discount->classname },
-                                     sub { my $m = shift->months_used;
-                                           $m =~ /\./ ? sprintf('%.2f',$m) : $m;
-                                         },
+                                     $months_used_sub,
                                      'otaker',
                                      'pkg',
                                      \&FS::UI::Web::cust_fields,
@@ -165,4 +163,9 @@ my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
 
 my $conf = new FS::Conf;
 
+my $months_used_sub = sub {
+  my $cust_pkg_discount = shift;
+  return 'Setup only' if $cust_pkg_discount->setuprecur eq 'setup';
+  return sprintf('%.2f', $cust_pkg_discount->months_used);
+};
 </%init>
diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html
index 4903e18..0c67843 100755
--- a/httemplate/view/cust_main/packages.html
+++ b/httemplate/view/cust_main/packages.html
@@ -42,6 +42,7 @@ table.hiddenrows {
   z-index: 1;
   text-align: center;
 }
+
 </STYLE>
 % # activate rolldown buttons for hidden package blocks
 <SCRIPT TYPE="text/javascript">
diff --git a/httemplate/view/cust_main/packages/package.html b/httemplate/view/cust_main/packages/package.html
index 8aa6403..e98b95e 100644
--- a/httemplate/view/cust_main/packages/package.html
+++ b/httemplate/view/cust_main/packages/package.html
@@ -53,10 +53,7 @@
                 ( <%onetime_change_link($cust_pkg)%> )
 %           }
 %           # also, you can discount it
-%           if ( $curuser->access_right('Discount customer package')
-%                && ! scalar($cust_pkg->cust_pkg_discount_active)
-%                && ! scalar($cust_pkg->part_pkg->part_pkg_discount)
-%              ) {
+%           if ( $can_discount_pkg ) {
               ( <%pkg_discount_link($cust_pkg)%> )
 %           }
           <BR>
@@ -79,14 +76,7 @@
                 ( <%pkg_change_link($cust_pkg)%> )
 %             } 
 %
-%             if ( $curuser->access_right('Discount customer package')
-%                  && $part_pkg->can_discount
-%                  && ! scalar( @{ $cust_pkg->{_cust_pkg_discount_active} } )
-%                  && (    ! $opt{'term_discounts'}
-%                       || ! scalar($cust_pkg->part_pkg->part_pkg_discount)
-%                     )
-%                )
-%             {
+%             if ( $can_discount_pkg ) {
 %               $br=1;
                 ( <%pkg_discount_link($cust_pkg)%> )
 %             }
@@ -437,4 +427,21 @@ sub pkg_event_link {
   '</a>';
 }
 
+# figure out if this user will be able to edit either the setup or recurring
+# discounts for this package
+my $can_discount_pkg = (
+  $part_pkg->can_discount
+  and
+  ( ( $curuser->access_right(['Discount customer package', 'Waive setup fee'])
+      and $cust_pkg->base_setup > 0
+      and !$cust_pkg->setup
+    )
+   or
+    ( $curuser->access_right('Discount customer package')
+      and $cust_pkg->base_recur > 0
+      and $cust_pkg->freq ne '0'
+    )
+  )
+);
+
 </%init>
diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html
index 7e125f7..13bd202 100644
--- a/httemplate/view/cust_main/packages/status.html
+++ b/httemplate/view/cust_main/packages/status.html
@@ -461,10 +461,11 @@ sub pkg_status_row_changed {
     my $part_pkg = $old->part_pkg;
     $html .= pkg_status_row_colspan(
       $cust_pkg, 
-      emt("Changed from [_1]: [_2]",
-             $cust_pkg->change_pkgnum,
-             $part_pkg->pkg_comment(cust_pkg=>$old, nopartpkg=>1)
-         ),
+#      emt("Changed from [_1]: [_2]",
+#             $cust_pkg->change_pkgnum,
+#             $part_pkg->pkg_comment(cust_pkg=>$old, nopartpkg=>1)
+#         ),
+      '',
       '',
       'size'    => '-1',
       'align'   => 'right',
@@ -529,17 +530,25 @@ sub pkg_status_row_discount {
 
     my $discount = $cust_pkg_discount->discount;
 
-    my $label = '<B>'.emt('Discount').'</B>: '. $discount->description;
-    if ( $discount->months ) {
+    my $label = '<SPAN STYLE="font-size: small"><B>';
+    if ( $cust_pkg_discount->setuprecur eq 'setup' ) {
+      $label .= emt('Setup Discount');
+    } else {
+      $label .= emt('Recurring Discount');
+    }
+    $label .= '</B>: '. $discount->description;
+    warn Dumper $cust_pkg_discount;
+    if ( $discount->months > 0 and $cust_pkg_discount->months_used > 0 ) {
       my $remaining = $discount->months - $cust_pkg_discount->months_used;
       $remaining = sprintf('%.2f', $remaining) if $remaining =~ /\./;
-      $label .= emt("([_1] months remaining)",$remaining);
+      $label .= <br> . emt("([_1] months remaining)",$remaining);
     }
+    $label .= '</SPAN>';
 
-    $label .= ' <FONT SIZE="-1">('.
-                '<A HREF="../misc/delete-cust_pkg_discount.html?'.
-                  $cust_pkg_discount->pkgdiscountnum.
-                '">'.emt('remove discount').'</A>)</FONT>';
+    #$label .= ' <FONT SIZE="-1">('.
+    #            '<A HREF="../misc/delete-cust_pkg_discount.html?'.
+    #              $cust_pkg_discount->pkgdiscountnum.
+    #            '">'.emt('remove discount').'</A>)</FONT>';
 
     $html .= pkg_status_row_colspan( $cust_pkg, $label, '', %opt );
 

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

Summary of changes:
 FS/FS/Schema.pm                                    |    1 +
 FS/FS/Template_Mixin.pm                            |   33 ++--
 FS/FS/cust_bill_pkg.pm                             |   11 +-
 FS/FS/cust_bill_pkg_discount.pm                    |   30 ++-
 FS/FS/cust_main/Packages.pm                        |    2 +-
 FS/FS/cust_pkg.pm                                  |   49 ++---
 FS/FS/cust_pkg_discount.pm                         |   64 ++++++-
 FS/FS/discount.pm                                  |   12 +-
 FS/FS/part_pkg/discount_Mixin.pm                   |   53 +++---
 httemplate/edit/cust_pkg_discount.html             |   66 ++++---
 httemplate/edit/process/cust_pkg_discount.html     |   96 ++++++++--
 httemplate/edit/process/quick-cust_pkg.cgi         |   29 +--
 .../{tr-select-months.html => select-months.html}  |    2 +-
 httemplate/elements/tr-select-months.html          |   16 +-
 httemplate/elements/tr-select-pkg-discount.html    |  196 ++++++++++++++++++++
 httemplate/misc/order_pkg.html                     |   17 +-
 httemplate/search/cust_bill_pkg_discount.html      |   11 +-
 httemplate/search/cust_pkg_discount.html           |    9 +-
 httemplate/view/cust_main/packages.html            |    1 +
 httemplate/view/cust_main/packages/package.html    |   31 ++--
 httemplate/view/cust_main/packages/status.html     |   31 ++--
 21 files changed, 566 insertions(+), 194 deletions(-)
 copy httemplate/elements/{tr-select-months.html => select-months.html} (90%)
 create mode 100644 httemplate/elements/tr-select-pkg-discount.html




More information about the freeside-commits mailing list