[freeside-commits] branch master updated. f6abf4cd6d8e7a0121124e9394b5f28f5bc4daa5

Mark Wells mark at 420.am
Sat Jan 31 14:48:09 PST 2015


The branch, master has been updated
       via  f6abf4cd6d8e7a0121124e9394b5f28f5bc4daa5 (commit)
      from  3d796bf211374d941bda1116ee27a0543045ea8a (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 f6abf4cd6d8e7a0121124e9394b5f28f5bc4daa5
Author: Mark Wells <mark at freeside.biz>
Date:   Sat Jan 31 14:44:16 2015 -0800

    discounts + quotations, #33099

diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index b7611c1..d5ed1b7 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1895,6 +1895,8 @@ sub tables_hashref {
         'contract_end',    @date_type,             '', '',
         'quantity',             'int', 'NULL', '', '', '',
         'waive_setup',         'char', 'NULL',  1, '', '', 
+        'unitsetup',     @money_typen,             '', '',
+        'unitrecur',     @money_typen,             '', '',
       ],
       'primary_key'  => 'quotationpkgnum',
       'unique'       => [],
@@ -1917,6 +1919,8 @@ sub tables_hashref {
         'quotationpkgdiscountnum', 'serial', '', '', '', '',
         'quotationpkgnum',            'int', '', '', '', '', 
         'discountnum',                'int', '', '', '', '',
+        'setup_amount',        @money_typen,         '', '',
+        'recur_amount',        @money_typen,         '', '',
         #'end_date',              @date_type,         '', '',
       ],
       'primary_key'  => 'quotationpkgdiscountnum',
diff --git a/FS/FS/TemplateItem_Mixin.pm b/FS/FS/TemplateItem_Mixin.pm
index 6ae3364..27b8f1b 100644
--- a/FS/FS/TemplateItem_Mixin.pm
+++ b/FS/FS/TemplateItem_Mixin.pm
@@ -367,15 +367,17 @@ sub cust_bill_pkg_detail {
 
 }
 
-=item cust_bill_pkg_discount 
+=item pkg_discount 
 
-Returns the list of associated cust_bill_pkg_discount objects.
+Returns the list of associated cust_bill_pkg_discount or 
+quotation_pkg_discount objects.
 
 =cut
 
-sub cust_bill_pkg_discount {
+sub pkg_discount {
   my $self = shift;
-  qsearch( $self->discount_table, { 'billpkgnum' => $self->billpkgnum } );
+  my $pkey = $self->primary_key;
+  qsearch( $self->discount_table, { $pkey => $self->get($pkey) } );
 }
 
 1;
diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm
index 9669ac2..e26592c 100644
--- a/FS/FS/Template_Mixin.pm
+++ b/FS/FS/Template_Mixin.pm
@@ -691,11 +691,12 @@ sub print_generic {
   # (this is used in the summary & on the payment coupon)
   $invoice_data{'balance'} = sprintf("%.2f", $balance_due);
 
-  # info from customer's last invoice before this one, for some 
-  # summary formats
-  $invoice_data{'last_bill'} = {};
+  # flag telling this invoice to have a first-page summary
+  my $summarypage = '';
 
   if ( $self->custnum && $self->invnum ) {
+    # XXX should be an FS::cust_bill method to set the defaults, instead
+    # of checking the type here
 
     my $last_bill = $self->previous_bill;
     if ( $last_bill ) {
@@ -801,13 +802,16 @@ sub print_generic {
       $invoice_data{'previous_payments'} = [];
       $invoice_data{'previous_credits'} = [];
     }
-  } # if this is an invoice
 
-  my $summarypage = '';
-  if ( $conf->exists('invoice_usesummary', $agentnum) ) {
-    $summarypage = 1;
-  }
-  $invoice_data{'summarypage'} = $summarypage;
+    # info from customer's last invoice before this one, for some 
+    # summary formats
+    $invoice_data{'last_bill'} = {};
+  
+    if ( $conf->exists('invoice_usesummary', $agentnum) ) {
+      $invoice_data{'summarypage'} = $summarypage = 1;
+    }
+
+  } # if this is an invoice
 
   warn "$me substituting variables in notes, footer, smallfooter\n"
     if $DEBUG > 1;
@@ -3093,7 +3097,9 @@ sub _items_cust_bill_pkg {
             if $cust_bill_pkg->recur != 0
             || $discount_show_always
             || $cust_bill_pkg->recur_show_zero;
-          push @b, {
+          #push @b, {
+          # keep it consistent, please
+          $s = {
             'pkgnum'      => $cust_bill_pkg->pkgpart, #so it displays in Ref
             'description' => $description,
             'amount'      => sprintf("%.2f", $cust_bill_pkg->setup),
@@ -3106,7 +3112,8 @@ sub _items_cust_bill_pkg {
           };
         }
         if ( $cust_bill_pkg->recur != 0 ) {
-          push @b, {
+          #push @b, {
+          $r = {
             'pkgnum'      => $cust_bill_pkg->pkgpart, #so it displays in Ref
             'description' => "$desc (". $cust_bill_pkg->part_pkg->freq_pretty.")",
             'amount'      => sprintf("%.2f", $cust_bill_pkg->recur),
@@ -3399,89 +3406,6 @@ sub _items_cust_bill_pkg {
 
         } # recurring or usage with recurring charge
 
-        # decide whether to show active discounts here
-        if (
-            # case 1: we are showing a single line for the package
-            ( !$type )
-            # case 2: we are showing a setup line for a package that has
-            # no base recurring fee
-            or ( $type eq 'S' and $cust_bill_pkg->unitrecur == 0 )
-            # case 3: we are showing a recur line for a package that has 
-            # a base recurring fee
-            or ( $type eq 'R' and $cust_bill_pkg->unitrecur > 0 )
-        ) {
-
-          # 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;
-          }
-
-          my @discounts = $cust_bill_pkg->cust_bill_pkg_discount;
-          # special case: if there are old "discount details" on this line 
-          # item, don't show discount line items
-          if ( FS::cust_bill_pkg_detail->count(
-              "detail LIKE 'Includes discount%' AND billpkgnum = " .
-              $cust_bill_pkg->billpkgnum
-             ) > 0 ) {
-             @discounts = ();
-          }
-          if ( @discounts ) {
-            warn "$me _items_cust_bill_pkg including discounts for ".
-              $cust_bill_pkg->billpkgnum."\n"
-              if $DEBUG;
-            my $discount_amount = sum( map {$_->amount} @discounts );
-            # if multiple discounts apply to the same package, how to display
-            # them? ext_description lines, apparently
-            #
-            # # discount amounts are negative
-            if ( $d and $cust_bill_pkg->hidden ) {
-              $d->{amount}      -= $discount_amount;
-            } else {
-              my @ext;
-              $d = {
-                _is_discount    => 1,
-                description     => $self->mt('Discount'),
-                amount          => -1 * $discount_amount,
-                ext_description => \@ext,
-              };
-              foreach my $cust_bill_pkg_discount (@discounts) {
-                my $discount = $cust_bill_pkg_discount->cust_pkg_discount->discount;
-                my $discount_desc = $discount->description_short;
-
-                if ($discount->months) {
-
-                  # calculate months remaining after this invoice
-                  my $used = FS::Record->scalar_sql(
-                    'SELECT SUM(months) FROM cust_bill_pkg_discount
-                      JOIN cust_bill_pkg USING (billpkgnum)
-                      JOIN cust_bill USING (invnum)
-                      WHERE pkgdiscountnum = ? AND _date <= ?',
-                    $cust_bill_pkg_discount->pkgdiscountnum,
-                    $self->_date
-                  );
-                  $used ||= 0;
-                  my $remaining = sprintf('%.2f', $discount->months - $used);
-                  # append "for X months (Y months remaining)"
-                  $discount_desc .= $self->mt(' for [quant,_1,month] ([quant,_2,month] remaining)',
-                    $cust_bill_pkg_discount->months,
-                    $remaining
-                  );
-                } # else it's not time-limited
-                push @ext, &{$escape_function}($discount_desc);
-              }
-            }
-
-            # update the active line (before the discount) to show the 
-            # original price (whether this is a hidden line or not)
-            $active_line->{amount} += $discount_amount;
-            
-          } # if there are any discounts
-        } # if this is an appropriate place to show discounts
-
       } else { # taxes and fees
 
         warn "$me _items_cust_bill_pkg cust_bill_pkg is tax\n"
@@ -3496,6 +3420,56 @@ sub _items_cust_bill_pkg {
 
       } # if quotation / package line item / other line item
 
+      # decide whether to show active discounts here
+      if (
+          # case 1: we are showing a single line for the package
+          ( !$type )
+          # case 2: we are showing a setup line for a package that has
+          # no base recurring fee
+          or ( $type eq 'S' and $cust_bill_pkg->unitrecur == 0 )
+          # case 3: we are showing a recur line for a package that has 
+          # a base recurring fee
+          or ( $type eq 'R' and $cust_bill_pkg->unitrecur > 0 )
+      ) {
+
+        my $item_discount = $cust_bill_pkg->_item_discount;
+        if ( $item_discount ) {
+          # $item_discount->{amount} is negative
+
+          if ( $d and $cust_bill_pkg->hidden ) {
+            $d->{amount}      += $item_discount->{amount};
+          } else {
+            $d = $item_discount;
+            $_ = &{$escape_function}($_) foreach @{ $d->{ext_description} };
+          }
+
+          # 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};
+
+          }
+
+        } # if there are any discounts
+      } # if this is an appropriate place to show discounts
+
     } # foreach $display
 
     $discount_show_always = ($cust_bill_pkg->cust_bill_pkg_discount
diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm
index 56a666e..352ed6a 100644
--- a/FS/FS/cust_bill_pkg.pm
+++ b/FS/FS/cust_bill_pkg.pm
@@ -668,6 +668,45 @@ sub units {
   $self->pkgnum ? $self->part_pkg->calc_units($self->cust_pkg) : 0; # 1?
 }
 
+=item _item_discount
+
+If this item has any discounts, returns a hashref in the format used
+by L<FS::Template_Mixin/_items_cust_bill_pkg> to describe the discount(s)
+on an invoice. This will contain the keys 'description', 'amount', 
+'ext_description' (an arrayref of text lines describing the discounts),
+and '_is_discount' (a flag).
+
+The value for 'amount' will be negative, and will be scaled for the package
+quantity.
+
+=cut
+
+sub _item_discount {
+  my $self = shift;
+  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
+  # show discount line items
+  if ( FS::cust_bill_pkg_detail->count("detail LIKE 'Includes discount%' AND billpkgnum = ?", $self->billpkgnum || 0) > 0 ) {
+    return;
+  } 
+  
+  my @ext;
+  my $d = {
+    _is_discount    => 1,
+    description     => $self->mt('Discount'),
+    amount          => 0,
+    ext_description => \@ext,
+    # maybe should show quantity/unit discount?
+  };
+  foreach my $pkg_discount (@pkg_discounts) {
+    push @ext, $pkg_discount->description;
+    $d->{amount} -= $pkg_discount->amount;
+  } 
+  $d->{amount} *= $self->quantity || 1;
+  
+  return $d;
+}
 
 =item set_display OPTION => VALUE ...
 
diff --git a/FS/FS/cust_bill_pkg_discount.pm b/FS/FS/cust_bill_pkg_discount.pm
index 534a067..9e64d20 100644
--- a/FS/FS/cust_bill_pkg_discount.pm
+++ b/FS/FS/cust_bill_pkg_discount.pm
@@ -126,6 +126,39 @@ Returns the associated line item (see L<FS::cust_bill_pkg>).
 
 Returns the associated customer discount (see L<FS::cust_pkg_discount>).
 
+=item description
+
+Returns a string describing the discount (for use on an invoice).
+
+=cut
+
+sub description {
+  my $self = shift;
+  my $discount = $self->cust_pkg_discount->discount;
+  my $desc = $discount->description_short;
+  $desc .= $self->mt(' each') if $self->cust_bill_pkg->quantity > 1;
+
+  if ($discount->months) {
+    # 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(
+      'SELECT SUM(months) FROM cust_bill_pkg_discount
+      JOIN cust_bill_pkg USING (billpkgnum)
+      JOIN cust_bill USING (invnum)
+      WHERE pkgdiscountnum = ? AND _date <= ?',
+      $self->pkgdiscountnum,
+      $date
+    );
+    $used ||= 0;
+    my $remaining = sprintf('%.2f', $discount->months - $used);
+    $desc .= $self->mt(' for [quant,_1,month] ([quant,_2,month] remaining)',
+              $self->months,
+              $remaining
+             );
+  }
+  return $desc;
+}
+
 =back
 
 =head1 BUGS
diff --git a/FS/FS/quotation.pm b/FS/FS/quotation.pm
index 5c94150..38e7318 100644
--- a/FS/FS/quotation.pm
+++ b/FS/FS/quotation.pm
@@ -631,6 +631,27 @@ sub search_sql_where {
 
 }
 
+=item _items_pkg
+
+Return line item hashes for each package on this quotation. Differs from the
+base L<FS::Template_Mixin> version in that it recalculates each quoted package
+first, and doesn't implement the "condensed" option.
+
+=cut
+
+sub _items_pkg {
+  my ($self, %options) = @_;
+  my @quotation_pkg = $self->quotation_pkg;
+  foreach (@quotation_pkg) {
+    my $error = $_->estimate;
+    die "error calculating estimate for pkgpart " . $_->pkgpart.": $error\n"
+      if $error;
+  }
+
+  # run it through the Template_Mixin engine
+  return $self->_items_cust_bill_pkg(\@quotation_pkg, %options);
+}
+
 =back
 
 =head1 BUGS
diff --git a/FS/FS/quotation_pkg.pm b/FS/FS/quotation_pkg.pm
index 33c761e..3813fb2 100644
--- a/FS/FS/quotation_pkg.pm
+++ b/FS/FS/quotation_pkg.pm
@@ -2,9 +2,10 @@ package FS::quotation_pkg;
 use base qw( FS::TemplateItem_Mixin FS::Record );
 
 use strict;
-use FS::Record qw( qsearchs ); #qsearch
+use FS::Record qw( qsearchs dbh ); #qsearch
 use FS::part_pkg;
 use FS::quotation_pkg_discount; #so its loaded when TemplateItem_Mixin needs it
+use List::Util qw(sum);
 
 =head1 NAME
 
@@ -39,19 +40,19 @@ primary key
 
 =item pkgpart
 
-pkgpart
+pkgpart (L<FS::part_pkg>) of the package
 
 =item locationnum
 
-locationnum
+locationnum (L<FS::cust_location>) where the package will be in service
 
 =item start_date
 
-start_date
+expected start date for the package, as a timestamp
 
 =item contract_end
 
-contract_end
+contract end date
 
 =item quantity
 
@@ -59,8 +60,15 @@ quantity
 
 =item waive_setup
 
-waive_setup
+'Y' to waive the setup fee
 
+=item unitsetup
+
+The amount per package that will be charged in setup/one-time fees.
+
+=item unitrecur
+
+The amount per package that will be charged per billing cycle.
 
 =back
 
@@ -93,10 +101,69 @@ sub discount_table        { 'quotation_pkg_discount'; }
 Adds this record to the database.  If there is an error, returns the error,
 otherwise returns false.
 
+=cut
+
+sub insert {
+  my ($self, %options) = @_;
+
+  my $dbh = dbh;
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+
+  my $error = $self->SUPER::insert;
+
+  if ( !$error and $self->discountnum ) {
+    $error = $self->insert_discount;
+    $error .= ' (setting discount)' if $error;
+  }
+
+  # update $self and any discounts with their amounts
+  if ( !$error ) {
+    $error = $self->estimate;
+    $error .= ' (calculating charges)' if $error;
+  }
+
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  } else {
+    $dbh->commit if $oldAutoCommit;
+    return '';
+  }
+}
+
 =item delete
 
 Delete this record from the database.
 
+=cut
+
+sub delete {
+  my $self = shift;
+
+  my $dbh = dbh;
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+
+  foreach ($self->quotation_pkg_discount) {
+    my $error = $_->delete;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error . ' (deleting discount)';
+    }
+  }
+
+  my $error = $self->SUPER::delete;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  } else {
+    $dbh->commit if $oldAutoCommit;
+    return '';
+  }
+  
+}
+
 =item replace OLD_RECORD
 
 Replaces the OLD_RECORD with this one in the database.  If there is an error,
@@ -121,8 +188,11 @@ sub check {
     || $self->ut_numbern('start_date')
     || $self->ut_numbern('contract_end')
     || $self->ut_numbern('quantity')
+    || $self->ut_moneyn('unitsetup')
+    || $self->ut_moneyn('unitrecur')
     || $self->ut_enum('waive_setup', [ '', 'Y'] )
   ;
+
   return $error if $error;
 
   $self->SUPER::check;
@@ -140,48 +210,159 @@ sub desc {
   $self->part_pkg->pkg;
 }
 
-sub setup {
+=item estimate
+
+Update the quotation_pkg record with the estimated setup and recurring 
+charges for the package. Returns nothing on success, or an error message
+on failure.
+
+=cut
+
+sub estimate {
   my $self = shift;
-  return '0.00' if $self->waive_setup eq 'Y' || $self->{'_NO_SETUP_KLUDGE'};
   my $part_pkg = $self->part_pkg;
-  #my $setup = $part_pkg->can('base_setup') ? $part_pkg->base_setup
-  #                                         : $part_pkg->option('setup_fee');
-  my $setup = $part_pkg->option('setup_fee');
-  #XXX discounts
-  $setup *= $self->quantity if $self->quantity;
-  sprintf('%.2f', $setup);
+  my $quantity = $self->quantity || 1;
+  my ($unitsetup, $unitrecur);
+  # calculate base fees
+  if ( $self->waive_setup eq 'Y' || $self->{'_NO_SETUP_KLUDGE'} ) {
+    $unitsetup = '0.00';
+  } else {
+    $unitsetup = $part_pkg->base_setup;
+  }
+  if ( $self->{'_NO_RECUR_KLUDGE'} ) {
+    $unitrecur = '0.00';
+  } else {
+    $unitrecur = $part_pkg->base_recur;
+  }
+
+  #XXX add-on packages
+
+  $self->set('unitsetup', $unitsetup);
+  $self->set('unitrecur', $unitrecur);
+  my $error = $self->replace;
+  return $error if $error;
+
+  # semi-duplicates calc_discount
+  my $setup_discount = 0;
+  my $recur_discount = 0;
+
+  my %setup_discounts; # quotationpkgdiscountnum => amount
+  my %recur_discounts; # quotationpkgdiscountnum => amount
+
+  # XXX the order of applying discounts is ill-defined, which matters
+  # if there are percentage and amount discounts on the same package.
+  foreach my $pkg_discount ($self->quotation_pkg_discount) {
+
+    my $discount = $pkg_discount->discount;
+    my $this_setup_discount = 0;
+    my $this_recur_discount = 0;
+
+    if ( $discount->percent > 0 ) {
+
+      if ( $discount->setup ) {
+        $this_setup_discount = ($discount->percent * $unitsetup / 100);
+      }
+      $this_recur_discount = ($discount->percent * $unitrecur / 100);
+
+    } elsif ( $discount->amount > 0 ) {
+
+      my $discount_left = $discount->amount;
+      if ( $discount->setup ) {
+        if ( $discount_left > $unitsetup - $setup_discount ) {
+          # then discount the setup to zero
+          $discount_left -= $unitsetup - $setup_discount;
+          $this_setup_discount = $unitsetup - $setup_discount;
+        } else {
+          # not enough discount to fully cover the setup
+          $this_setup_discount = $discount_left;
+          $discount_left = 0;
+        }
+      }
+      # same logic for recur
+      if ( $discount_left > $unitrecur - $recur_discount ) {
+        $this_recur_discount = $unitrecur - $recur_discount;
+      } else {
+        $this_recur_discount = $discount_left;
+      }
+
+    }
+
+    # increment the total discountage
+    $setup_discount += $this_setup_discount;
+    $recur_discount += $this_recur_discount;
+    # and update the pkg_discount object
+    $pkg_discount->set('setup_amount', sprintf('%.2f', $setup_discount));
+    $pkg_discount->set('recur_amount', sprintf('%.2f', $recur_discount));
+    my $error = $pkg_discount->replace;
+    return $error if $error;
+  }
 
+  '';
 }
 
-sub recur {
+=item insert_discount
+
+Associates this package with a discount (see L<FS::cust_pkg_discount>,
+possibly inserting a new discount on the fly (see L<FS::discount>). Properties
+of the discount will be taken from this object.
+
+=cut
+
+sub insert_discount {
+  #my ($self, %options) = @_;
   my $self = shift;
-  return '0.00' if $self->{'_NO_RECUR_KLUDGE'};
-  my $part_pkg = $self->part_pkg;
-  my $recur = $part_pkg->can('base_recur') ? $part_pkg->base_recur($self)
-                                           : $part_pkg->option('recur_fee');
-  #XXX discounts
-  $recur *= $self->quantity if $self->quantity;
-  sprintf('%.2f', $recur);
+
+  my $cust_pkg_discount = FS::quotation_pkg_discount->new( {
+    'quotationpkgnum' => $self->quotationpkgnum,
+    'discountnum'     => $self->discountnum,
+    #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,
+  } );
+
+  $cust_pkg_discount->insert;
 }
 
-sub unitsetup {
+sub _item_discount {
   my $self = shift;
-  return '0.00' if $self->waive_setup eq 'Y' || $self->{'_NO_SETUP_KLUDGE'};
-  my $part_pkg = $self->part_pkg;
-  my $setup = $part_pkg->option('setup_fee');
+  my @pkg_discounts = $self->pkg_discount;
+  return if @pkg_discounts == 0;
+  
+  my @ext;
+  my $d = {
+    _is_discount    => 1,
+    description     => $self->mt('Discount'),
+    setup_amount    => 0,
+    recur_amount    => 0,
+    amount          => 0,
+    ext_description => \@ext,
+    # maybe should show quantity/unit discount?
+  };
+  foreach my $pkg_discount (@pkg_discounts) {
+    push @ext, $pkg_discount->description;
+    $d->{setup_amount} -= $pkg_discount->setup_amount;
+    $d->{recur_amount} -= $pkg_discount->recur_amount;
+  } 
+  $d->{setup_amount} *= $self->quantity || 1;
+  $d->{recur_amount} *= $self->quantity || 1;
+  $d->{amount} = $d->{setup_amount} + $d->{recur_amount};
+  
+  return $d;
+}
 
-  #XXX discounts
-  sprintf('%.2f', $setup);
+sub setup {
+  my $self = shift;
+  ($self->unitsetup - sum(map { $_->setup_amount } $self->pkg_discount))
+    * ($self->quantity || 1);
 }
 
-sub unitrecur {
+sub recur {
   my $self = shift;
-  return '0.00' if $self->{'_NO_RECUR_KLUDGE'};
-  my $part_pkg = $self->part_pkg;
-  my $recur = $part_pkg->can('base_recur') ? $part_pkg->base_recur
-                                           : $part_pkg->option('recur_fee');
-  #XXX discounts
-  sprintf('%.2f', $recur);
+  ($self->unitrecur - sum(map { $_->recur_amount } $self->pkg_discount))
+    * ($self->quantity || 1);
 }
 
 =item part_pkg_currency_option OPTIONNAME
@@ -273,6 +454,8 @@ sub prospect_main {
 
 =head1 BUGS
 
+Doesn't support taxes, fees, or add-on packages.
+
 =head1 SEE ALSO
 
 L<FS::Record>, schema.html from the base documentation.
diff --git a/FS/FS/quotation_pkg_discount.pm b/FS/FS/quotation_pkg_discount.pm
index 19930ac..633308c 100644
--- a/FS/FS/quotation_pkg_discount.pm
+++ b/FS/FS/quotation_pkg_discount.pm
@@ -1,5 +1,6 @@
 package FS::quotation_pkg_discount;
 use base qw( FS::Record );
+use FS::Maketext 'mt'; # XXX not really correct
 
 use strict;
 
@@ -36,12 +37,21 @@ primary key
 
 =item quotationpkgnum
 
-quotationpkgnum
+quotationpkgnum of the L<FS::quotation_pkg> record that this discount is
+for.
 
 =item discountnum
 
-discountnum
+discountnum (L<FS::discount>)
 
+=item setup_amount
+
+Amount that will be discounted from setup fees, per package quantity.
+
+=item recur_amount
+
+Amount that will be discounted from recurring fees in the first billing
+cycle, per package quantity.
 
 =back
 
@@ -107,6 +117,8 @@ sub check {
     $self->ut_numbern('quotationpkgdiscountnum')
     || $self->ut_foreign_key('quotationpkgnum', 'quotation_pkg', 'quotationpkgnum' )
     || $self->ut_foreign_key('discountnum', 'discount', 'discountnum' )
+    || $self->ut_moneyn('setup_amount')
+    || $self->ut_moneyn('recur_amount')
   ;
   return $error if $error;
 
@@ -115,6 +127,39 @@ sub check {
 
 =back
 
+=item amount
+
+Returns the total amount of this discount (setup + recur), for compatibility
+with L<FS::cust_bill_pkg_discount>.
+
+=cut
+
+sub amount {
+  my $self = shift;
+  return $self->get('setup_amount') + $self->get('recur_amount');
+}
+
+=item description
+
+Returns a string describing the discount (for use on the quotation).
+
+=cut
+
+sub description {
+  my $self = shift;
+  my $discount = $self->discount;
+  my $desc = $discount->description_short;
+  # XXX localize to prospect language, once prospects get languages
+  $desc .= mt(' each') if $self->quotation_pkg->quantity > 1;
+
+  if ($discount->months) {
+    # unlike cust_bill_pkg_discount, there are no "months remaining"; it 
+    # hasn't started yet.
+    $desc .= mt(' (for [quant,_1,month])', $discount->months);
+  }
+  return $desc;
+}
+
 =head1 BUGS
 
 =head1 SEE ALSO
diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi
index f1d8c26..34f5d12 100644
--- a/httemplate/edit/process/quick-cust_pkg.cgi
+++ b/httemplate/edit/process/quick-cust_pkg.cgi
@@ -159,7 +159,7 @@ if ( $quotationnum ) {
   $quotation_pkg->prospectnum($prospect_main->prospectnum) if $prospect_main;
 
   #XXX handle new location
-  $error = $quotation_pkg->insert;
+  $error = $quotation_pkg->insert || $quotation_pkg->estimate;
 
 } else {
 


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

Summary of changes:
 FS/FS/Schema.pm                            |    4 +
 FS/FS/TemplateItem_Mixin.pm                |   10 +-
 FS/FS/Template_Mixin.pm                    |  162 ++++++++----------
 FS/FS/cust_bill_pkg.pm                     |   39 +++++
 FS/FS/cust_bill_pkg_discount.pm            |   33 ++++
 FS/FS/quotation.pm                         |   21 +++
 FS/FS/quotation_pkg.pm                     |  253 ++++++++++++++++++++++++----
 FS/FS/quotation_pkg_discount.pm            |   49 +++++-
 httemplate/edit/process/quick-cust_pkg.cgi |    2 +-
 9 files changed, 437 insertions(+), 136 deletions(-)




More information about the freeside-commits mailing list