From 7a33cb6e4c3e33b7399d6574cbd3ee38ddcba5e0 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Wed, 7 Dec 2016 15:27:49 -0800 Subject: [PATCH] specify Avalara tax product for per-line taxes, #73063 --- FS/FS/Schema.pm | 5 +++ FS/FS/TaxEngine/billsoft.pm | 54 +++++++++--------------- FS/FS/part_pkg.pm | 14 ++++++ FS/FS/part_pkg_taxproduct.pm | 32 +++++++++++--- httemplate/browse/part_pkg.cgi | 8 +++- httemplate/edit/part_pkg.cgi | 44 ++++--------------- httemplate/elements/tr-part_pkg-taxproducts.html | 4 +- 7 files changed, 84 insertions(+), 77 deletions(-) diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index f8b82f454..0e41b1afe 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -3248,6 +3248,7 @@ sub tables_hashref { 'adjourn_months', 'int', 'NULL', '', '', '', 'contract_end_months','int','NULL', '', '', '', 'change_to_pkgpart', 'int', 'NULL', '', '', '', + 'units_taxproductnum','int','NULL', '', '', '', ], 'primary_key' => 'pkgpart', 'unique' => [], @@ -3265,6 +3266,10 @@ sub tables_hashref { { columns => [ 'taxproductnum' ], table => 'part_pkg_taxproduct', }, + { columns => [ 'units_taxproductnum' ], + table => 'part_pkg_taxproduct', + references => [ 'taxproductnum' ], + }, { columns => [ 'agentnum' ], table => 'agent', }, diff --git a/FS/FS/TaxEngine/billsoft.pm b/FS/FS/TaxEngine/billsoft.pm index 69717a22d..9147f5cc6 100644 --- a/FS/FS/TaxEngine/billsoft.pm +++ b/FS/FS/TaxEngine/billsoft.pm @@ -188,8 +188,7 @@ sub create_batch { # cache some things my (%cust_pkg, %part_pkg, %cust_location, %classname); # keys are transaction codes (the first part of the taxproduct string) - # and then locationnums; for per-location taxes - my %sales; + my %all_tcodes; my @options = $self->conf->config('billsoft-taxconfig'); @@ -239,8 +238,7 @@ sub create_batch { my $taxproduct = $self->part_pkg_taxproduct($part_pkg, $classnum) or next; - my $tcode = substr($taxproduct, 0, 6); - my $scode = substr($taxproduct, 6, 6); + my ($tcode, $scode) = split(':', $taxproduct); # For CDRs, use the call termination site rather than setting # Termination fields to the service address. @@ -259,9 +257,6 @@ sub create_batch { } # while $cdr = $cdr_search->fetch - my $recur_tcode; - # now write lines for the non-CDR portion of the charges - my $locationnum = $cust_pkg->locationnum; # use termination address for the service location @@ -284,13 +279,10 @@ sub create_batch { my $taxproduct = $self->part_pkg_taxproduct($part_pkg, $_); next unless $taxproduct; - my $tcode = substr($taxproduct, 0, 6); - my $scode = substr($taxproduct, 6, 6); - $sales{$tcode} ||= 0; - $recur_tcode = $tcode if $_ eq 'recur'; + my ($tcode, $scode) = split(':', $taxproduct); + $all_tcodes{$tcode} ||= 1; my $price = $cust_bill_pkg->get($_); - $sales{$tcode} += $price; $price -= $usage_total if $_ eq 'recur'; @@ -305,10 +297,9 @@ sub create_batch { } # foreach (setup, recur) - # S-code 21: taxes based on number of lines (E911, mostly) - # voip_cdr and voip_inbound packages know how to report this. Not all - # T-codes are eligible for this; only report it if the /21 taxproduct - # exists. + # taxes based on number of lines (E911, mostly) + # mostly S-code 21 but can be others, as they want to know about + # Centrex trunks, PBX extensions, etc. # # (note: the nomenclature of "service" and "transaction" codes is # backward from the way most people would use the terms. you'd think @@ -318,25 +309,18 @@ sub create_batch { # to avoid confusion.) # XXX cache me - # XXX this isn't precisely correct. Local exchange service on - # high-capacity trunks, Centrex, and PBX trunks are supposed to be - # reported as three separate implicit transactions: number of trunks, - # of outbound channels, of extensions. - # This is also true for VoIP PBX trunks. Come back to this. - if ( $recur_tcode ) { - my $lines_taxproduct = FS::part_pkg_taxproduct->count( - 'data_vendor = \'billsoft\' and taxproduct = ?', - sprintf('%06d%06d', $recur_tcode, 21) - ); + if ( my $lines_taxproduct = $part_pkg->units_taxproduct ) { my $lines = $cust_bill_pkg->units; - - if ( $lines_taxproduct and $lines ) { + my $taxproduct = $lines_taxproduct->taxproduct; + my ($tcode, $scode) = split(':', $taxproduct); + $all_tcodes{$tcode} ||= 1; + if ( $lines ) { $csv->print_hr($fh, { %pkg_properties, %termination, RequestType => 'CalcTaxes', - TransactionType => $recur_tcode, - ServiceType => 21, + TransactionType => $tcode, + ServiceType => $scode, Charge => 0, Lines => $lines, } ); @@ -345,12 +329,16 @@ sub create_batch { } # foreach my $cust_bill_pkg - foreach my $tcode (keys %sales) { + foreach my $tcode (keys %all_tcodes) { - # S-code 43: per-invoice tax (apparently this is a thing) + # S-code 43: per-invoice tax + # XXX not exactly correct; there's "Invoice Bundle" (7:94) and + # "Centrex Invoice" (7:623). Local Exchange service would benefit from + # more high-level selection of the tax properties. (Infer from the FCC + # reporting options?) my $invoice_taxproduct = FS::part_pkg_taxproduct->count( 'data_vendor = \'billsoft\' and taxproduct = ?', - sprintf('%06d%06d', $tcode, 43) + $tcode . ':43' ); if ( $invoice_taxproduct ) { $csv->print_hr($fh, { diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index 35f178e25..ae63487ec 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -735,6 +735,7 @@ sub check { || $self->ut_floatn('pay_weight') || $self->ut_floatn('credit_weight') || $self->ut_numbern('taxproductnum') + || $self->ut_numbern('units_taxproductnum') || $self->ut_foreign_keyn('classnum', 'pkg_class', 'classnum') || $self->ut_foreign_keyn('addon_classnum', 'pkg_class', 'classnum') || $self->ut_foreign_keyn('taxproductnum', @@ -1731,6 +1732,19 @@ sub taxproduct_description { $part_pkg_taxproduct ? $part_pkg_taxproduct->description : ''; } +=item units_taxproduct + +Returns the L record used to report the taxable +service units (usually phone lines) on this package. + +=cut + +sub units_taxproduct { + my $self = shift; + $self->units_taxproductnum + ? FS::part_pkg_taxproduct->by_key($self->units_taxproductnum) + : ''; +} =item tax_rates DATA_PROVIDER, GEOCODE, [ CLASS ] diff --git a/FS/FS/part_pkg_taxproduct.pm b/FS/FS/part_pkg_taxproduct.pm index e86d0285a..51bc37f9c 100644 --- a/FS/FS/part_pkg_taxproduct.pm +++ b/FS/FS/part_pkg_taxproduct.pm @@ -223,7 +223,8 @@ sub batch_import { my $imported = 0; my $csv = Text::CSV_XS->new; - # fields: taxproduct, description + my $error; + # for importing the "transervdesc.txt" file while ( my $row = $csv->getline($fh) ) { if (!defined $row) { $dbh->rollback if $oldAutoCommit; @@ -236,15 +237,32 @@ sub batch_import { ); } - my $new = FS::part_pkg_taxproduct->new({ - 'data_vendor' => 'billsoft', - 'taxproduct' => $row->[0], - 'description' => $row->[1], + # columns 0-2: irrelevant here + my $taxproduct = $row->[3] . ':' . $row->[5]; + my $description = $row->[4]; + $description =~ s/\s+$//; + $description .= ':' . $row->[6]; + $description =~ s/\s+$//; + my $ppt = qsearchs('part_pkg_taxproduct', { + 'data_vendor' => 'billsoft', + 'taxproduct' => $taxproduct }); - my $error = $new->insert; + if ( $ppt ) { + $ppt->set('description', $description); + $ppt->set('note', $row->[7]); + $error = $ppt->replace; + } else { + $ppt = FS::part_pkg_taxproduct->new({ + 'data_vendor' => 'billsoft', + 'taxproduct' => $taxproduct, + 'description' => $description, + 'note' => $row->[7], + }); + $error = $ppt->insert; + } if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "error inserting part_pkg_taxproduct: $error\n"; + return "error inserting part_pkg_taxproduct $taxproduct: $error\n"; } $imported++; } diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi index acc32113f..8c51b35f4 100755 --- a/httemplate/browse/part_pkg.cgi +++ b/httemplate/browse/part_pkg.cgi @@ -601,12 +601,18 @@ if ( $taxclasses ) { { 'data' => &$taxproduct_sub($base_ppt), 'align' => 'right' }, ]; } + if ( my $units_ppt = $part_pkg->units_taxproduct ) { + push @$out, [ + { 'data' => emt('Lines'), 'align' => 'left' }, + { 'data' => &$taxproduct_sub($units_ppt), 'align' => 'right' }, + ]; + } for (my $i = 0; $i < scalar @classnums; $i++) { my $num = $part_pkg->option('usage_taxproductnum_' . $classnums[$i]); next if !$num; my $ppt = FS::part_pkg_taxproduct->by_key($num); push @$out, [ - { 'data' => $classnames[$i] . ': ', 'align' => 'left', }, + { 'data' => $classnames[$i], 'align' => 'left', }, { 'data' => &$taxproduct_sub($ppt), 'align' => 'right' }, ]; } diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index 64a75252e..84aac5bde 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -40,7 +40,6 @@ 'setuptax' => 'Setup fee tax exempt', 'recurtax' => 'Recurring fee tax exempt', 'taxclass' => 'Tax class', - 'taxproduct_select'=> 'Tax products', 'plan' => 'Price plan', 'disabled' => 'Disable new orders', 'disable_line_item_date_ranges' => 'Disable line item date ranges', @@ -73,6 +72,7 @@ 'contract_end_months' => 'Contract ends after ', 'expire_months' => 'Cancel the package after ', 'change_to_pkgpart'=> 'and replace it with ', + 'units_taxproductnum' => 'Per-line tax product', }, 'fields' => [ @@ -214,28 +214,15 @@ type => 'hidden', value => join(',', @taxproductnums), }, - #{ field => 'taxproduct_select', - # type => 'selectlayers', - # options => [ '(default)', @taxproductnums ], - # curr_value => '(default)', - # labels => { ( '(default)' => '(default)' ), - # map {($_=>$usage_class{$_})} - # @taxproductnums - # }, - # layer_fields => \%taxproduct_fields, - # layer_values_callback => $taxproduct_values, - # layers_only => !$taxproducts, - # cell_style => ( !$taxproducts - # ? 'display:none' - # : '' - # ), - #}, { field => 'taxproductnum', type => 'part_pkg-taxproducts', include_opt_callback => sub { pkgpart => $_[0]->pkgpart }, }, - + { field => 'units_taxproductnum', + type => ($tax_data_vendor ? + 'select-taxproduct' : 'hidden'), + }, { type => 'tablebreak-tr-title', value => 'Promotions', #better name? }, @@ -445,7 +432,7 @@ my $agent_clone_extra_sql = ' ) '; my $conf = new FS::Conf; -my $taxproducts = $conf->config('tax_data_vendor') ne ''; +my $tax_data_vendor = $conf->config('tax_data_vendor'); my $fcc_opts = $conf->exists('part_pkg-show_fcc_options'); @@ -1112,13 +1099,8 @@ my $html_bottom = sub { my $return = include('/elements/selectlayers.html', %selectlayers, 'layers_only'=>1 ). ''; + include('/elements/selectlayers.html', %selectlayers, 'js_only'=>1 ) . + ''; $return; @@ -1199,16 +1181,8 @@ my $field_callback = sub { my $field = $fieldref->{field}; if ($field eq 'taxproductnums') { $fieldref->{value} = join(',', @taxproductnums); - } elsif ($field eq 'taxproduct_select') { - $fieldref->{options} = [ '(default)', @taxproductnums ]; - $fieldref->{labels} = { ( '(default)' => '(default)' ), - map {( $_ => ($usage_class{$_} || $_) )} - @taxproductnums - }; - $fieldref->{layer_fields} = \%taxproduct_fields; - $fieldref->{layer_values_callback} = $taxproduct_values; } elsif ($field eq 'taxproductnum') { # part_pkg-taxproduct, new style - if ( !$taxproducts ) { + if ( !$tax_data_vendor ) { # then make the widget go away $fieldref->{type} = 'hidden'; } diff --git a/httemplate/elements/tr-part_pkg-taxproducts.html b/httemplate/elements/tr-part_pkg-taxproducts.html index 5dcea09f1..50dace729 100644 --- a/httemplate/elements/tr-part_pkg-taxproducts.html +++ b/httemplate/elements/tr-part_pkg-taxproducts.html @@ -54,7 +54,8 @@ my %pkg_options; if ($pkgpart) { my $part_pkg = FS::part_pkg->by_key($pkgpart); %pkg_options = $part_pkg->options; - $curr_values{''} = $part_pkg->taxproductnum; + $curr_values{''} = $cgi->param('taxproductnum') + || $part_pkg->taxproductnum; } foreach my $usage_class (@classes) { @@ -66,4 +67,5 @@ foreach my $usage_class (@classes) { $curr_values{$classnum} = $curr_value; $separate = 1 if ( length($classnum) and length($curr_value) ); } + -- 2.11.0