[freeside-commits] branch master updated. 53ed077b4901be22b3ca5405a093364b6f6b6407

Mark Wells mark at 420.am
Fri Mar 6 15:00:20 PST 2015


The branch, master has been updated
       via  53ed077b4901be22b3ca5405a093364b6f6b6407 (commit)
       via  35c18f29bc29dedfe2fa4ef037390d90b17f87ba (commit)
      from  92acdafd28e41e0e333d2e9df59af657f1f1242c (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 53ed077b4901be22b3ca5405a093364b6f6b6407
Author: Mark Wells <mark at freeside.biz>
Date:   Fri Mar 6 15:00:00 2015 -0800

    estimate tax on quotations, #32489

diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index cd4f01d..6a156a5 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1980,7 +1980,12 @@ sub tables_hashref {
       'primary_key' => 'quotationtaxnum',,
       'unique' => [],
       'index'  => [ [ 'quotationpkgnum' ] ],
-    },
+      'foreign_keys' => [
+                          { columns    => [ 'quotationpkgnum' ],
+                            table      => 'quotation_pkg',
+                          },
+                        ],
+},
 
     'cust_location' => { #'location' now that its prospects too, but...
       'columns' => [
diff --git a/FS/FS/cust_main_county.pm b/FS/FS/cust_main_county.pm
index 075ac32..652ff33 100644
--- a/FS/FS/cust_main_county.pm
+++ b/FS/FS/cust_main_county.pm
@@ -277,7 +277,7 @@ sub taxline {
   my $cust_bill = $taxables->[0]->cust_bill;
   my $custnum   = $cust_bill ? $cust_bill->custnum : $opt{'custnum'};
   my $invoice_time = $cust_bill ? $cust_bill->_date : $opt{'invoice_time'};
-  my $cust_main = FS::cust_main->by_key($custnum) if $custnum > 0;
+  my $cust_main = FS::cust_main->by_key($custnum) if $custnum;
   # (to avoid complications with estimated tax on quotations, assume it's
   # taxable if there is no customer)
   #if (!$cust_main) {
@@ -285,18 +285,21 @@ sub taxline {
   #}
 
   # set a flag if the customer is tax-exempt
-  my $exempt_cust;
+  my ($exempt_cust, $exempt_cust_taxname);
   my $conf = FS::Conf->new;
-  if ( $conf->exists('cust_class-tax_exempt') ) {
-    my $cust_class = $cust_main->cust_class;
-    $exempt_cust = $cust_class->tax if $cust_class;
-  } else {
-    $exempt_cust = $cust_main->tax;
-  }
+  if ( $cust_main ) {
+    if ( $conf->exists('cust_class-tax_exempt') ) {
+      my $cust_class = $cust_main->cust_class;
+      $exempt_cust = $cust_class->tax if $cust_class;
+    } else {
+      $exempt_cust = $cust_main->tax;
+    }
 
-  # set a flag if the customer is exempt from this tax here
-  my $exempt_cust_taxname = $cust_main->tax_exemption($self->taxname)
-    if $self->taxname;
+    # set a flag if the customer is exempt from this tax here
+    if ( $self->taxname ) {
+      $exempt_cust_taxname = $cust_main->tax_exemption($self->taxname);
+    }
+  }
 
   # Gather any exemptions that are already attached to these cust_bill_pkgs
   # so that we can deduct them from the customer's monthly limit.
@@ -320,9 +323,8 @@ sub taxline {
     my $part_pkg  = $cust_bill_pkg->part_pkg;
     my $part_fee  = $cust_bill_pkg->part_fee;
 
-    my $locationnum = $cust_pkg
-                      ? $cust_pkg->locationnum
-                      : $cust_main->bill_locationnum;
+    my $locationnum = $cust_bill_pkg->tax_locationnum
+                      || $cust_main->ship_locationnum;
 
     my @new_exemptions;
     my $taxable_charged = $cust_bill_pkg->setup + $cust_bill_pkg->recur
diff --git a/FS/FS/quotation_pkg.pm b/FS/FS/quotation_pkg.pm
index c579e35..1c4766e 100644
--- a/FS/FS/quotation_pkg.pm
+++ b/FS/FS/quotation_pkg.pm
@@ -442,7 +442,7 @@ Returns the customer (L<FS::cust_main> object).
 
 sub cust_main {
   my $self = shift;
-  my $quotation = FS::quotation->by_key($self->quotationnum) or return '';
+  my $quotation = $self->quotation or return '';
   $quotation->cust_main;
 }
 
@@ -454,20 +454,10 @@ Returns the prospect (L<FS::prospect_main> object).
 
 sub prospect_main {
   my $self = shift;
-  my $quotation = FS::quotation->by_key($self->quotationnum) or return '';
+  my $quotation = $self->quotation or return '';
   $quotation->prospect_main;
 }
 
-sub quotation_pkg_tax {
-  my $self = shift;
-  qsearch('quotation_pkg_tax', { quotationpkgnum => $self->quotationpkgnum });
-}
-
-sub cust_location {
-  my $self = shift;
-  $self->locationnum ? qsearchs('cust_location', { locationnum => $self->locationnum }) : '';
-}
-
 =back
 
 =head1 BUGS
diff --git a/FS/FS/quotation_pkg_tax.pm b/FS/FS/quotation_pkg_tax.pm
index 3d1dceb..f459ed2 100644
--- a/FS/FS/quotation_pkg_tax.pm
+++ b/FS/FS/quotation_pkg_tax.pm
@@ -104,12 +104,6 @@ sub check {
   $self->SUPER::check;
 }
 
-#stub for 3.x
-sub quotation_pkg {
-  my $self = shift;
-  FS::quotation_pkg->by_key($self->quotationpkgnum);
-}
-
 =back
 
 =head1 SEE ALSO

commit 35c18f29bc29dedfe2fa4ef037390d90b17f87ba
Author: Mark Wells <mark at freeside.biz>
Date:   Fri Mar 6 14:20:22 2015 -0800

    estimate tax on quotations, #32489

diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index 897b781..2cabf85 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -399,6 +399,7 @@ if ( -e $addl_handler_use_file ) {
   use FS::prospect_contact;
   use FS::cust_contact;
   use FS::legacy_cust_history;
+  use FS::quotation_pkg_tax;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 89fc5f7..cd4f01d 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1967,6 +1967,21 @@ sub tables_hashref {
                         ],
     },
 
+    'quotation_pkg_tax' => {
+      'columns' => [
+        'quotationtaxnum',  'serial',     '',      '', '', '',
+        'quotationpkgnum',     'int',     '',      '', '', '',
+        'itemdesc',        'varchar',     '', $char_d, '', '',
+        'taxnum',              'int',     '',      '', '', '', 
+        'taxtype',         'varchar',     '', $char_d, '', '',
+        'setup_amount',    @money_type,                '', '',
+        'recur_amount',    @money_type,                '', '',
+      ],
+      'primary_key' => 'quotationtaxnum',,
+      'unique' => [],
+      'index'  => [ [ 'quotationpkgnum' ] ],
+    },
+
     'cust_location' => { #'location' now that its prospects too, but...
       'columns' => [
         'locationnum',      'serial',     '',      '', '', '',
diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm
index 9045291..ed6c8e0 100644
--- a/FS/FS/Template_Mixin.pm
+++ b/FS/FS/Template_Mixin.pm
@@ -1266,7 +1266,7 @@ sub print_generic {
               ];
 
   }
-  
+ 
   if ( @items_tax ) {
     my $total = {};
     $total->{'total_item'} = $self->mt('Sub-total');
@@ -1316,13 +1316,12 @@ sub print_generic {
 
   if ( $self->can('_items_total') ) { # quotations
 
-    $self->_items_total(\@total_items);
+    my @new_total_items = $self->_items_total;
 
-    foreach ( @total_items ) {
+    foreach ( @new_total_items ) {
       $_->{'total_item'}   = &$embolden_function( $_->{'total_item'} );
-      $_->{'total_amount'} = &$embolden_function( $other_money_char.
-                                                   $_->{'total_amount'}
-                                                );
+      $_->{'total_amount'} = &$embolden_function( $other_money_char.$_->{'total_amount'});
+      push @total_items, $_;
     }
 
   } else { #normal invoice case
@@ -1544,7 +1543,7 @@ sub print_generic {
   # invoice history "section" (not really a section)
   # not to be included in any subtotals, completely independent of 
   # everything...
-  if ( $conf->exists('previous_invoice_history') ) {
+  if ( $conf->exists('previous_invoice_history') and $cust_main->isa('FS::cust_main') ) {
     my %history;
     my %monthorder;
     foreach my $cust_bill ( $cust_main->cust_bill ) {
@@ -3094,6 +3093,7 @@ sub _items_cust_bill_pkg {
                         );
 
       if ( ref($cust_bill_pkg) eq 'FS::quotation_pkg' ) {
+        # XXX this should be pulled out into quotation_pkg
 
         warn "$me _items_cust_bill_pkg cust_bill_pkg is quotation_pkg\n"
           if $DEBUG > 1;
diff --git a/FS/FS/cust_main_county.pm b/FS/FS/cust_main_county.pm
index 654e567..075ac32 100644
--- a/FS/FS/cust_main_county.pm
+++ b/FS/FS/cust_main_county.pm
@@ -278,10 +278,11 @@ sub taxline {
   my $custnum   = $cust_bill ? $cust_bill->custnum : $opt{'custnum'};
   my $invoice_time = $cust_bill ? $cust_bill->_date : $opt{'invoice_time'};
   my $cust_main = FS::cust_main->by_key($custnum) if $custnum > 0;
-  if (!$cust_main) {
-    # better way to handle this?  should we just assume that it's taxable?
-    die "unable to calculate taxes for an unknown customer\n";
-  }
+  # (to avoid complications with estimated tax on quotations, assume it's
+  # taxable if there is no customer)
+  #if (!$cust_main) {
+    #die "unable to calculate taxes for an unknown customer\n";
+  #}
 
   # set a flag if the customer is tax-exempt
   my $exempt_cust;
@@ -313,6 +314,7 @@ sub taxline {
   my @tax_location;
 
   foreach my $cust_bill_pkg (@$taxables) {
+    # careful... may be a cust_bill_pkg or a quotation_pkg
 
     my $cust_pkg  = $cust_bill_pkg->cust_pkg;
     my $part_pkg  = $cust_bill_pkg->part_pkg;
@@ -379,7 +381,11 @@ sub taxline {
     }
   
     if ( $self->exempt_amount && $self->exempt_amount > 0 
-      and $taxable_charged > 0 ) {
+      and $taxable_charged > 0
+      and $cust_main ) {
+
+      # XXX monthly exemptions currently don't work on quotations
+
       # If the billing period extends across multiple calendar months, 
       # there may be several months of exemption available.
       my $sdate = $cust_bill_pkg->sdate || $invoice_time;
@@ -493,7 +499,7 @@ sub taxline {
         }
 
       }
-    } # if exempt_amount
+    } # if exempt_amount and $cust_main
 
     $_->taxnum($self->taxnum) foreach @new_exemptions;
 
diff --git a/FS/FS/quotation.pm b/FS/FS/quotation.pm
index 9cef3c1..55730ae 100644
--- a/FS/FS/quotation.pm
+++ b/FS/FS/quotation.pm
@@ -7,10 +7,12 @@ use Tie::RefHash;
 use FS::CurrentUser;
 use FS::UID qw( dbh );
 use FS::Maketext qw( emt );
-use FS::Record qw( qsearchs );
+use FS::Record qw( qsearch qsearchs );
+use FS::Conf;
 use FS::cust_main;
 use FS::cust_pkg;
 use FS::quotation_pkg;
+use FS::quotation_pkg_tax;
 use FS::type_pkgs;
 
 =head1 NAME
@@ -248,34 +250,87 @@ sub cust_or_prospect_label_link {
 
 }
 
-#prevent things from falsely showing up as taxes, at least until we support
-# quoting tax amounts..
 sub _items_tax {
-  return ();
+  ();
 }
+
 sub _items_nontax {
   shift->cust_bill_pkg;
 }
 
 sub _items_total {
-  my( $self, $total_items ) = @_;
+  my $self = shift;
+  $self->quotationnum =~ /^(\d+)$/ or return ();
+
+  my @items;
+
+  # show taxes in here also; the setup/recurring breakdown is different
+  # from what Template_Mixin expects
+  my @setup_tax = qsearch({
+      select      => 'itemdesc, SUM(setup_amount) as setup_amount',
+      table       => 'quotation_pkg_tax',
+      addl_from   => ' JOIN quotation_pkg USING (quotationpkgnum) ',
+      extra_sql   => ' WHERE quotationnum = '.$1,
+      order_by    => ' GROUP BY itemdesc HAVING SUM(setup_amount) > 0' .
+                     ' ORDER BY itemdesc',
+  });
+  # recurs need to be grouped by frequency, and to have a pkgpart
+  my @recur_tax = qsearch({
+      select      => 'freq, itemdesc, SUM(recur_amount) as recur_amount, MAX(pkgpart) as pkgpart',
+      table       => 'quotation_pkg_tax',
+      addl_from   => ' JOIN quotation_pkg USING (quotationpkgnum)'.
+                     ' JOIN part_pkg USING (pkgpart)',
+      extra_sql   => ' WHERE quotationnum = '.$1,
+      order_by    => ' GROUP BY freq, itemdesc HAVING SUM(recur_amount) > 0' .
+                     ' ORDER BY freq, itemdesc',
+  });
 
-  if ( $self->total_setup > 0 ) {
-    push @$total_items, {
+  my $total_setup = $self->total_setup;
+  foreach my $pkg_tax (@setup_tax) {
+    if ($pkg_tax->setup_amount > 0) {
+      $total_setup += $pkg_tax->setup_amount;
+      push @items, {
+        'total_item'    => $pkg_tax->itemdesc . ' ' . $self->mt('(setup)'),
+        'total_amount'  => $pkg_tax->setup_amount,
+      };
+    }
+  }
+
+  if ( $total_setup > 0 ) {
+    push @items, {
       'total_item'   => $self->mt( $self->total_recur > 0 ? 'Total Setup' : 'Total' ),
-      'total_amount' => $self->total_setup,
+      'total_amount' => sprintf('%.2f',$total_setup),
+      'break_after'  => ( scalar(@recur_tax) ? 1 : 0 )
     };
   }
 
   #could/should add up the different recurring frequencies on lines of their own
   # but this will cover the 95% cases for now
-  if ( $self->total_recur > 0 ) {
-    push @$total_items, {
+  my $total_recur = $self->total_recur;
+  # label these with the frequency
+  foreach my $pkg_tax (@recur_tax) {
+    if ($pkg_tax->recur_amount > 0) {
+      $total_recur += $pkg_tax->recur_amount;
+      # an arbitrary part_pkg, but with the right frequency
+      # XXX localization
+      my $part_pkg = qsearchs('part_pkg', { pkgpart => $pkg_tax->pkgpart });
+      push @items, {
+        'total_item'    => $pkg_tax->itemdesc . ' (' .  $part_pkg->freq_pretty . ')',
+        'total_amount'  => $pkg_tax->recur_amount,
+      };
+    }
+  }
+
+  if ( $total_recur > 0 ) {
+    push @items, {
       'total_item'   => $self->mt('Total Recurring'),
-      'total_amount' => $self->total_recur,
+      'total_amount' => sprintf('%.2f',$total_recur),
+      'break_after'  => 1,
     };
   }
 
+  return @items;
+
 }
 
 =item enable_previous
@@ -396,7 +451,7 @@ sub charge {
     $cust_pkg_ref = exists($_[0]->{cust_pkg_ref}) ? $_[0]->{cust_pkg_ref} : '';
     $bill_now = exists($_[0]->{bill_now}) ? $_[0]->{bill_now} : '';
     $invoice_terms = exists($_[0]->{invoice_terms}) ? $_[0]->{invoice_terms} : '';
-    $locationnum = $_[0]->{locationnum} || $self->ship_locationnum;
+    $locationnum = $_[0]->{locationnum};
   } else {
     $amount     = shift;
     $setup_cost = '';
@@ -517,6 +572,139 @@ sub enable {
   $self->replace();
 }
 
+=item estimate
+
+Calculates current prices for all items on this quotation, including 
+discounts and taxes, and updates the quotation_pkg records accordingly.
+
+=cut
+
+sub estimate {
+  my $self = shift;
+  my $conf = FS::Conf->new;
+
+  my $dbh = dbh;
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+
+  # bring individual items up to date (set setup/recur and discounts)
+  my @quotation_pkg = $self->quotation_pkg;
+  foreach my $pkg (@quotation_pkg) {
+    my $error = $pkg->estimate;
+    if ($error) {
+      $dbh->rollback if $oldAutoCommit;
+      die "error calculating estimate for pkgpart " . $pkg->pkgpart.": $error\n";
+    }
+
+    # delete old tax records
+    foreach my $quotation_pkg_tax ($pkg->quotation_pkg_tax) {
+      $error = $quotation_pkg_tax->delete;
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        die "error flushing tax records for pkgpart ". $pkg->pkgpart.": $error\n";
+      }
+    }
+  }
+
+  # annoyingly duplicates handle_taxes--fix this in 4.x 
+  if ( $conf->exists('enable_taxproducts') ) {
+    warn "can't calculate external taxes for quotations yet\n";
+    # then we're done
+    return;
+  }
+
+  my %taxnum_exemptions; # for monthly exemptions; as yet unused
+
+  foreach my $pkg (@quotation_pkg) {
+    my $location = $pkg->cust_location;
+
+    my $part_item = $pkg->part_pkg; # we don't have fees on these yet
+    my @loc_keys = qw( district city county state country);
+    my %taxhash = map { $_ => $location->$_ } @loc_keys;
+    $taxhash{'taxclass'} = $part_item->taxclass;
+    my @taxes;
+    my %taxhash_elim = %taxhash;
+    my @elim = qw( district city county state );
+    do {
+      @taxes = qsearch( 'cust_main_county', \%taxhash_elim );
+      if ( !scalar(@taxes) && $taxhash_elim{'taxclass'} ) {
+        #then try a match without taxclass
+        my %no_taxclass = %taxhash_elim;
+        $no_taxclass{ 'taxclass' } = '';
+        @taxes = qsearch( 'cust_main_county', \%no_taxclass );
+      }
+    
+      $taxhash_elim{ shift(@elim) } = '';
+    } while ( !scalar(@taxes) && scalar(@elim) );
+
+    foreach my $tax_def (@taxes) {
+      my $taxnum = $tax_def->taxnum;
+      $taxnum_exemptions{$taxnum} ||= [];
+
+      # XXX do some kind of equivalent to set_exemptions here
+      # but for now just declare that there are no exemptions,
+      # and then hack the taxable amounts if the package def
+      # excludes setup/recur
+      $pkg->set('cust_tax_exempt_pkg', []);
+
+      if ( $part_item->setuptax or $tax_def->setuptax ) {
+        $pkg->set('unitsetup', 0);
+      }
+      if ( $part_item->recurtax or $tax_def->recurtax ) {
+        $pkg->set('unitrecur', 0);
+      }
+
+      my %taxline;
+      foreach my $pass (qw(first recur)) {
+        if ($pass eq 'recur') {
+          $pkg->set('unitsetup', 0);
+        }
+
+        my $taxline = $tax_def->taxline(
+          [ $pkg ],
+          exemptions => $taxnum_exemptions{$taxnum}
+        );
+        if ($taxline and !ref($taxline)) {
+          $dbh->rollback if $oldAutoCommit;
+          die "error calculating '".$tax_def->taxname .
+              "' for pkgpart '".$pkg->pkgpart."': $taxline\n";
+        }
+        $taxline{$pass} = $taxline;
+      }
+
+      my $quotation_pkg_tax = FS::quotation_pkg_tax->new({
+          quotationpkgnum => $pkg->quotationpkgnum,
+          itemdesc        => $tax_def->taxname,
+          taxnum          => $taxnum,
+          taxtype         => ref($tax_def),
+      });
+      my $setup_amount = 0;
+      my $recur_amount = 0;
+      if ($taxline{first}) {
+        $setup_amount = $taxline{first}->setup; # "first cycle", not setup
+      }
+      if ($taxline{recur}) {
+        $recur_amount = $taxline{recur}->setup;
+        $setup_amount -= $recur_amount; # to get the actual setup amount
+      }
+      if ( $recur_amount > 0 or $setup_amount > 0 ) {
+        $quotation_pkg_tax->set('setup_amount', sprintf('%.2f', $setup_amount));
+        $quotation_pkg_tax->set('recur_amount', sprintf('%.2f', $recur_amount));
+
+        my $error = $quotation_pkg_tax->insert;
+        if ($error) {
+          $dbh->rollback if $oldAutoCommit;
+          die "error recording '".$tax_def->taxname .
+              "' for pkgpart '".$pkg->pkgpart."': $error\n";
+        } # if $error
+      } # else there are no non-zero taxes; continue
+    } # foreach $tax_def
+  } # foreach $pkg
+
+  $dbh->commit if $oldAutoCommit;
+  '';
+}
+
 =back
 
 =head1 CLASS METHODS
@@ -649,15 +837,9 @@ first, and doesn't implement the "condensed" option.
 
 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;
-  }
-
+  $self->estimate;
   # run it through the Template_Mixin engine
-  return $self->_items_cust_bill_pkg(\@quotation_pkg, %options);
+  return $self->_items_cust_bill_pkg([ $self->quotation_pkg ], %options);
 }
 
 =back
diff --git a/FS/FS/quotation_pkg.pm b/FS/FS/quotation_pkg.pm
index 25edc94..c579e35 100644
--- a/FS/FS/quotation_pkg.pm
+++ b/FS/FS/quotation_pkg.pm
@@ -145,7 +145,7 @@ sub delete {
   my $oldAutoCommit = $FS::UID::AutoCommit;
   local $FS::UID::AutoCommit = 0;
 
-  foreach ($self->quotation_pkg_discount) {
+  foreach ($self->quotation_pkg_discount, $self->quotation_pkg_tax) {
     my $error = $_->delete;
     if ( $error ) {
       $dbh->rollback if $oldAutoCommit;
@@ -358,13 +358,13 @@ sub _item_discount {
 
 sub setup {
   my $self = shift;
-  ($self->unitsetup - sum(map { $_->setup_amount } $self->pkg_discount))
+  ($self->unitsetup - sum(0, map { $_->setup_amount } $self->pkg_discount))
     * ($self->quantity || 1);
 }
 
 sub recur {
   my $self = shift;
-  ($self->unitrecur - sum(map { $_->recur_amount } $self->pkg_discount))
+  ($self->unitrecur - sum(0, map { $_->recur_amount } $self->pkg_discount))
     * ($self->quantity || 1);
 }
 
@@ -417,6 +417,11 @@ sub cust_bill_pkg_display {
     $recur->{'type'} = 'R';
 
     if ( $type eq 'S' ) {
+sub tax_locationnum {
+  my $self = shift;
+  $self->locationnum;
+}
+
       return ($setup);
     } elsif ( $type eq 'R' ) {
       return ($recur);
@@ -453,11 +458,21 @@ sub prospect_main {
   $quotation->prospect_main;
 }
 
+sub quotation_pkg_tax {
+  my $self = shift;
+  qsearch('quotation_pkg_tax', { quotationpkgnum => $self->quotationpkgnum });
+}
+
+sub cust_location {
+  my $self = shift;
+  $self->locationnum ? qsearchs('cust_location', { locationnum => $self->locationnum }) : '';
+}
+
 =back
 
 =head1 BUGS
 
-Doesn't support taxes, fees, or add-on packages.
+Doesn't support fees, or add-on packages.
 
 =head1 SEE ALSO
 
diff --git a/FS/FS/quotation_pkg_tax.pm b/FS/FS/quotation_pkg_tax.pm
new file mode 100644
index 0000000..3d1dceb
--- /dev/null
+++ b/FS/FS/quotation_pkg_tax.pm
@@ -0,0 +1,122 @@
+package FS::quotation_pkg_tax;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_main_county;
+use FS::quotation_pkg;
+
+=head1 NAME
+
+FS::quotation_pkg_tax - Object methods for quotation_pkg_tax records
+
+=head1 SYNOPSIS
+
+  use FS::quotation_pkg_tax;
+
+  $record = new FS::quotation_pkg_tax \%hash;
+  $record = new FS::quotation_pkg_tax { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::quotation_pkg_tax object represents tax on a quoted package. 
+FS::quotation_pkg_tax inherits from FS::Record (though it should eventually
+inherit from some shared superclass of L<FS::cust_bill_pkg_tax_location>). 
+The following fields are currently supported:
+
+=over 4
+
+=item quotationtaxnum - primary key
+
+=item quotationpkgnum - the L<FS::quotation_pkg> record that the tax applies 
+to.
+
+=item itemdesc - the name of the tax
+
+=item taxnum - the L<FS::cust_main_county> or L<FS::tax_rate> defining the 
+tax.
+
+=item taxtype - the class of the tax rate represented by C<taxnum>.
+
+=item setup_amount - the amount of tax calculated on one-time charges
+
+=item recur_amount - the amount of tax calculated on recurring charges
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new estimated tax amount.  To add the record to the database, 
+see L<"insert">.
+
+=cut
+
+sub table { 'quotation_pkg_tax'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid example.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('quotationtaxnum')
+    || $self->ut_foreign_key('quotationpkgnum', 'quotation_pkg', 'quotationpkgnum')
+    || $self->ut_text('itemdesc')
+    || $self->ut_number('taxnum')
+    || $self->ut_enum('taxtype', [ 'FS::cust_main_county', 'FS::tax_rate' ])
+    || $self->ut_money('setup_amount')
+    || $self->ut_money('recur_amount')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+#stub for 3.x
+sub quotation_pkg {
+  my $self = shift;
+  FS::quotation_pkg->by_key($self->quotationpkgnum);
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/MANIFEST b/FS/MANIFEST
index 378421c..2ea404f 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -838,3 +838,5 @@ FS/pkg_discount_Mixin.pm
 t/pkg_discount_Mixin.t
 FS/legacy_cust_history.pm
 t/legacy_cust_history.t
+FS/quotation_pkg_tax.pm
+t/quotation_pkg_tax.t
diff --git a/FS/t/quotation_pkg_tax.t b/FS/t/quotation_pkg_tax.t
new file mode 100644
index 0000000..448884b
--- /dev/null
+++ b/FS/t/quotation_pkg_tax.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::quotation_pkg_tax;
+$loaded=1;
+print "ok 1\n";
diff --git a/conf/quotation_html b/conf/quotation_html
index a05bb60..44164b6 100644
--- a/conf/quotation_html
+++ b/conf/quotation_html
@@ -231,8 +231,10 @@
 
       foreach my $line ( @total_items ) {
 
+        # in the quotation logic, we specifically tag the total line
+        # instead of using "the second one from the bottom"
         $style .= 'border-bottom: 3px solid #000000;'
-          if ++$linenum == scalar(@total_items) - ( $balance_due_below_line ? 1 : 0 );
+          if $line->{break_after};
 
         $OUT .=
           '<tr class="invoice_totaldesc">';
diff --git a/conf/quotation_latex b/conf/quotation_latex
index 7ebc38d..6aac160 100644
--- a/conf/quotation_latex
+++ b/conf/quotation_latex
@@ -281,18 +281,16 @@
         }
       }
 
-      #if ($section == $sections[$#sections]) {
-        foreach my $line (grep {$_->{section}->{description} eq $section->{description}} @total_items) {
-          if ($section->{total_line_generator}) {
-            $OUT .= &{$section->{total_line_generator}}($line);
-          } else {
-            $OUT .= '\FStotaldesc{' . $line->{'total_item'} . '}' .
-                    '{' . $line->{'total_amount'} . '}' . "\n";
-          }
+      foreach my $line (grep {$_->{section}->{description} eq $section->{description}} @total_items) {
+        if ($section->{total_line_generator}) {
+          $OUT .= &{$section->{total_line_generator}}($line);
+        } else {
+          $OUT .= '\FStotaldesc{' . $line->{'total_item'} . '}' .
+                  '{' . $line->{'total_amount'} . '}' . "\n";
+          $OUT .= '\hline' . "\n" if $line->{'break_after'};
         }
-      #}
+      }
 
-      $OUT .= '\hline';
       $OUT .= '\endlastfoot';
 
       my $lastref = 0;

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

Summary of changes:
 FS/FS/Mason.pm                              |    1 +
 FS/FS/Schema.pm                             |   20 +++
 FS/FS/Template_Mixin.pm                     |   14 +-
 FS/FS/cust_main_county.pm                   |   48 +++---
 FS/FS/quotation.pm                          |  222 ++++++++++++++++++++++++---
 FS/FS/quotation_pkg.pm                      |   17 +-
 FS/FS/quotation_pkg_tax.pm                  |  116 ++++++++++++++
 FS/MANIFEST                                 |    2 +
 FS/t/{AccessRight.t => quotation_pkg_tax.t} |    2 +-
 conf/quotation_html                         |    4 +-
 conf/quotation_latex                        |   18 +--
 11 files changed, 399 insertions(+), 65 deletions(-)
 create mode 100644 FS/FS/quotation_pkg_tax.pm
 copy FS/t/{AccessRight.t => quotation_pkg_tax.t} (78%)




More information about the freeside-commits mailing list