discounts + quotations, #33099
authorMark Wells <mark@freeside.biz>
Sat, 31 Jan 2015 22:44:16 +0000 (14:44 -0800)
committerMark Wells <mark@freeside.biz>
Sat, 31 Jan 2015 22:44:22 +0000 (14:44 -0800)
FS/FS/Schema.pm
FS/FS/TemplateItem_Mixin.pm
FS/FS/Template_Mixin.pm
FS/FS/cust_bill_pkg.pm
FS/FS/cust_bill_pkg_discount.pm
FS/FS/quotation.pm
FS/FS/quotation_pkg.pm
FS/FS/quotation_pkg_discount.pm
httemplate/edit/process/quick-cust_pkg.cgi

index b7611c1..d5ed1b7 100644 (file)
@@ -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',
index 6ae3364..27b8f1b 100644 (file)
@@ -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;
index 9669ac2..e26592c 100644 (file)
@@ -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
index 56a666e..352ed6a 100644 (file)
@@ -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 ...
 
index 534a067..9e64d20 100644 (file)
@@ -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
index 5c94150..38e7318 100644 (file)
@@ -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
index 33c761e..3813fb2 100644 (file)
@@ -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.
index 19930ac..633308c 100644 (file)
@@ -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
index f1d8c26..34f5d12 100644 (file)
@@ -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 {