[freeside-commits] branch FREESIDE_3_BRANCH updated. 1c2774bbe1aefb37fb69c8643443c02a650282f8

Mark Wells mark at 420.am
Sat Jan 31 16:32:04 PST 2015


The branch, FREESIDE_3_BRANCH has been updated
       via  1c2774bbe1aefb37fb69c8643443c02a650282f8 (commit)
       via  f7ee9056e343a1e0f576c2b2989fb066decb8264 (commit)
       via  d46254f9b36873e457424eefdcf3610b71ef889d (commit)
      from  b2d79135127e961869a05b936b9d33b1bef135df (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 1c2774bbe1aefb37fb69c8643443c02a650282f8
Author: Mark Wells <mark at freeside.biz>
Date:   Sat Jan 31 16:29:38 2015 -0800

    discounts + quotations, #33099

diff --git a/FS/FS/quotation_pkg.pm b/FS/FS/quotation_pkg.pm
index 28677d0..914e1ce 100644
--- a/FS/FS/quotation_pkg.pm
+++ b/FS/FS/quotation_pkg.pm
@@ -2,7 +2,7 @@ package FS::quotation_pkg;
 
 use strict;
 use base qw( FS::TemplateItem_Mixin FS::Record );
-use FS::Record qw( qsearchs dbh ); #qsearch
+use FS::Record qw( qsearch qsearchs dbh );
 use FS::part_pkg;
 use FS::cust_location;
 use FS::quotation;
@@ -227,7 +227,7 @@ sub estimate {
   if ( $self->waive_setup eq 'Y' || $self->{'_NO_SETUP_KLUDGE'} ) {
     $unitsetup = '0.00';
   } else {
-    $unitsetup = $part_pkg->base_setup;
+    $unitsetup = $part_pkg->option('setup_fee',1) || '0.00'; # XXX 3.x only
   }
   if ( $self->{'_NO_RECUR_KLUDGE'} ) {
     $unitrecur = '0.00';
@@ -414,6 +414,18 @@ sub cust_main {
   $quotation->cust_main;
 }
 
+#stub for 3.x
+
+sub quotation {
+  my $self = shift;
+  FS::quotation->by_key($self->quotationnum);
+}
+
+sub quotation_pkg_discount {
+  my $self = shift;
+  qsearch('quotation_pkg_discount', { quotationpkgnum => $self->quotationpkgnum });
+}
+
 =back
 
 =head1 BUGS
diff --git a/FS/FS/quotation_pkg_discount.pm b/FS/FS/quotation_pkg_discount.pm
index 15b1bfe..24cb204 100644
--- a/FS/FS/quotation_pkg_discount.pm
+++ b/FS/FS/quotation_pkg_discount.pm
@@ -3,6 +3,8 @@ package FS::quotation_pkg_discount;
 use strict;
 use base qw( FS::Record );
 use FS::Record qw( qsearch qsearchs );
+use FS::quotation_pkg;
+use FS::discount;
 use FS::Maketext 'mt';
 
 =head1 NAME
@@ -161,6 +163,17 @@ sub description {
   return $desc;
 }
 
+#stub for 3.x
+sub quotation_pkg {
+  my $self = shift;
+  FS::quotation_pkg->by_key($self->quotationpkgnum);
+}
+
+sub discount {
+  my $self = shift;
+  FS::discount->by_key($self->discountnum);
+}
+
 =head1 BUGS
 
 =head1 SEE ALSO


commit f7ee9056e343a1e0f576c2b2989fb066decb8264
Author: Mark Wells <mark at freeside.biz>
Date:   Sat Jan 31 15:29:30 2015 -0800

    give discounts a default name, #31273

diff --git a/FS/FS/discount.pm b/FS/FS/discount.pm
index 4c9898a..1491139 100644
--- a/FS/FS/discount.pm
+++ b/FS/FS/discount.pm
@@ -175,7 +175,12 @@ sub description_short {
   my $conf = new FS::Conf;
   my $money_char = $conf->config('money_char') || '$';  
 
-  my $desc = $self->name ? $self->name.': ' : '';
+  my $desc;
+  if ( $self->name ) {
+    $desc = $self->name . ': ';
+  } else {
+    $desc = 'Discount of ';
+  }
   $desc .= $money_char. sprintf('%.2f/month', $self->amount)
     if $self->amount > 0;
 


commit d46254f9b36873e457424eefdcf3610b71ef889d
Author: Mark Wells <mark at freeside.biz>
Date:   Sat Jan 31 15:28:52 2015 -0800

    fix sprintf error, mostly #31273

diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index ef381e5..6301df2 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1336,6 +1336,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' => [],
@@ -1347,6 +1349,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 1fed7f1..986b308 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;
@@ -1172,6 +1176,12 @@ sub print_generic {
            join(', ', map "$_=>".$line_item->{$_}, keys %$line_item). "\n"
         if $DEBUG > 1;
 
+      push @buf, ( [ $line_item->{'description'},
+                     $money_char. sprintf("%10.2f", $line_item->{'amount'}),
+                   ],
+                   map { [ " ". $_, '' ] } @{$line_item->{'ext_description'}},
+                 );
+
       $line_item->{'ref'} = $line_item->{'pkgnum'};
       $line_item->{'product_code'} = $line_item->{'pkgpart'} || 'N/A'; # mt()?
       $line_item->{'section'} = $section;
@@ -1184,11 +1194,6 @@ sub print_generic {
       $line_item->{'ext_description'} ||= [];
  
       push @detail_items, $line_item;
-      push @buf, ( [ $line_item->{'description'},
-                     $money_char. sprintf("%10.2f", $line_item->{'amount'}),
-                   ],
-                   map { [ " ". $_, '' ] } @{$line_item->{'ext_description'}},
-                 );
     }
 
     if ( $section->{'description'} ) {
@@ -3092,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),
@@ -3105,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),
@@ -3398,68 +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 $def = $cust_bill_pkg_discount->cust_pkg_discount->discount;
-                push @ext, &{$escape_function}( $def->description );
-              }
-            }
-
-            # 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"
@@ -3474,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 d87e137..4718d18 100644
--- a/FS/FS/cust_bill_pkg.pm
+++ b/FS/FS/cust_bill_pkg.pm
@@ -671,6 +671,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 dfa83d3..1d6866a 100644
--- a/FS/FS/cust_bill_pkg_discount.pm
+++ b/FS/FS/cust_bill_pkg_discount.pm
@@ -144,6 +144,39 @@ sub 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 b3595ef..a8cbfeb 100644
--- a/FS/FS/quotation.pm
+++ b/FS/FS/quotation.pm
@@ -656,6 +656,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 cc45a85..28677d0 100644
--- a/FS/FS/quotation_pkg.pm
+++ b/FS/FS/quotation_pkg.pm
@@ -2,11 +2,12 @@ package FS::quotation_pkg;
 
 use strict;
 use base qw( FS::TemplateItem_Mixin FS::Record );
-use FS::Record qw( qsearchs ); #qsearch
+use FS::Record qw( qsearchs dbh ); #qsearch
 use FS::part_pkg;
 use FS::cust_location;
 use FS::quotation;
 use FS::quotation_pkg_discount; #so its loaded when TemplateItem_Mixin needs it
+use List::Util qw(sum);
 
 =head1 NAME
 
@@ -41,19 +42,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
 
@@ -61,8 +62,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
 
@@ -95,10 +103,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,
@@ -123,8 +190,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
-                                           : $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 cust_bill_pkg_display [ type => TYPE ]
@@ -237,6 +418,8 @@ sub cust_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 34e13a6..15b1bfe 100644
--- a/FS/FS/quotation_pkg_discount.pm
+++ b/FS/FS/quotation_pkg_discount.pm
@@ -3,6 +3,7 @@ package FS::quotation_pkg_discount;
 use strict;
 use base qw( FS::Record );
 use FS::Record qw( qsearch qsearchs );
+use FS::Maketext 'mt';
 
 =head1 NAME
 
@@ -37,12 +38,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
 
@@ -108,6 +118,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;
 
@@ -116,6 +128,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 38d5c44..67cdb87 100644
--- a/httemplate/edit/process/quick-cust_pkg.cgi
+++ b/httemplate/edit/process/quick-cust_pkg.cgi
@@ -144,7 +144,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                    |  152 ++++++++--------
 FS/FS/cust_bill_pkg.pm                     |   39 ++++
 FS/FS/cust_bill_pkg_discount.pm            |   33 ++++
 FS/FS/discount.pm                          |    7 +-
 FS/FS/quotation.pm                         |   21 +++
 FS/FS/quotation_pkg.pm                     |  265 ++++++++++++++++++++++++----
 FS/FS/quotation_pkg_discount.pm            |   62 ++++++-
 httemplate/edit/process/quick-cust_pkg.cgi |    2 +-
 10 files changed, 474 insertions(+), 121 deletions(-)




More information about the freeside-commits mailing list