summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Wells <mark@freeside.biz>2016-12-07 15:27:49 -0800
committerMark Wells <mark@freeside.biz>2016-12-07 15:27:58 -0800
commit7a33cb6e4c3e33b7399d6574cbd3ee38ddcba5e0 (patch)
treeb46feaff7d6c842e2ee3be38d71b684d33d7b7f2
parentecd038f7ae5c1ffc929f3c928ecd161eeb45d9be (diff)
specify Avalara tax product for per-line taxes, #73063
-rw-r--r--FS/FS/Schema.pm5
-rw-r--r--FS/FS/TaxEngine/billsoft.pm54
-rw-r--r--FS/FS/part_pkg.pm14
-rw-r--r--FS/FS/part_pkg_taxproduct.pm32
-rwxr-xr-xhttemplate/browse/part_pkg.cgi8
-rwxr-xr-xhttemplate/edit/part_pkg.cgi44
-rw-r--r--httemplate/elements/tr-part_pkg-taxproducts.html4
7 files changed, 84 insertions, 77 deletions
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index f8b82f4..0e41b1a 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 69717a2..9147f5c 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 35f178e..ae63487 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<FS::part_pkg_taxproduct> 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 e86d028..51bc37f 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 acc3211..8c51b35 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 64a7525..84aac5b 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 ).
'<SCRIPT TYPE="text/javascript">'.
- include('/elements/selectlayers.html', %selectlayers, 'js_only'=>1 );
-
-# $return .=
-# "taxproduct_selectchanged(document.getElementById('taxproduct_select'));\n"
-# if $taxproducts;
-
- $return .= '</SCRIPT>';
+ include('/elements/selectlayers.html', %selectlayers, 'js_only'=>1 ) .
+ '</SCRIPT>';
$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 5dcea09..50dace7 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) );
}
+
</%init>