use FS::cust_location;
use FS::cust_bill_pkg;
use FS::tax_rate;
-use JSON;
+use Cpanel::JSON::XS;
use Geo::StreetAddress::US;
-our $DEBUG = 2;
-our $json = JSON->new->pretty(1);
+our $DEBUG = 0;
+our $json = Cpanel::JSON::XS->new->pretty(1);
our $conf;
#}
# Avalara address standardization would be nice but isn't necessary
-# XXX this is just here to avoid reworking the framework right now. By the
-# 4.0 release, ALL tax calculations should be done after the invoice has
-# been inserted into the database.
-
# nothing to do here
sub add_sale {}
sub build_request {
my ($self, %opt) = @_;
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
my $cust_bill = $self->{cust_bill};
my $cust_main = $cust_bill->cust_main;
};
push @lines, $line;
}
+ # don't make the request unless there are some eligible line items
+ return '' if !@lines;
# assemble address records for any cust_locations we used here, plus
# the company address
my $our_address = join(' ',
$conf->config('company_address', $cust_main->agentnum)
);
- my $company_address = Geo::StreetAddress::US->parse_address($our_address);
+ my $company_address = Geo::StreetAddress::US->parse_location($our_address);
+ if (!$company_address->{street}
+ or !$company_address->{city}
+ or !$company_address->{zip}) {
+ die "Your company address could not be parsed. Avalara tax calculation requires a company address with street, city, and zip code.\n";
+ }
+
my $address1 = join(' ', grep $_, @{$company_address}{qw(
number prefix street type suffix
)});
# create the top level object
my $date = DateTime->from_epoch(epoch => $self->{invoice_time});
+ my $doctype = $self->{estimate} ? 'SalesOrder' : 'SalesInvoice';
return {
'CustomerCode' => $cust_main->custnum,
'DocDate' => $date->strftime('%Y-%m-%d'),
'DocCode' => $cust_bill->invnum,
'DetailLevel' => 'Tax',
'Commit' => 'false',
- 'DocType' => 'SalesInvoice', # ???
+ 'DocType' => $doctype,
'CustomerUsageType' => $cust_main->taxstatus,
# ExemptionNo, Discount, TaxOverride, PurchaseOrderNo,
'Addresses' => \@addrs,
my $cust_bill = shift;
if (!$cust_bill->invnum) {
- warn "FS::TaxEngine::avalara: can't calculate taxes on a non-inserted invoice";
- return;
+ # then something is wrong
+ die "FS::TaxEngine::avalara: can't calculate taxes on a non-inserted invoice\n";
}
$self->{cust_bill} = $cust_bill;
# assemble the request hash
my $request = $self->build_request;
+ if (!$request) {
+ warn "no tax-eligible items on this invoice\n" if $DEBUG;
+ return [];
+ }
warn "sending Avalara tax request\n" if $DEBUG;
my $request_json = $json->encode($request);
my %tax_item_named;
if ( $response->{ResultCode} ne 'Success' ) {
- return "invoice#".$cust_bill->invnum.": ".
- join("\n", @{ $response->{Messages} });
+ die "Avalara tax error on invoice#".$cust_bill->invnum.": ".
+ join("\n", @{ $response->{Messages} }).
+ "\n";
}
warn "creating taxes for inv#$invnum\n" if $DEBUG > 1;
foreach my $TaxLine (@{ $response->{TaxLines} }) {
fee => 0,
});
my $error = $tax_rate->find_or_insert;
- return "error inserting tax_rate record for '$taxname': $error\n"
+ die "error inserting tax_rate record for '$taxname': $error\n"
if $error;
+ $tax_rate = $tax_rate->replace_old; # get its taxnum if there wasn't one
# create a tax_rate_location record
my $tax_rate_location = FS::tax_rate_location->new({
# country?
});
$error = $tax_rate_location->find_or_insert;
- return "error inserting tax_rate_location record for ".
+ die "error inserting tax_rate_location record for ".
$TaxDetail->{JurisCode} .": $error\n"
if $error;
# create a link record
my $tax_link = FS::cust_bill_pkg_tax_rate_location->new({
- cust_bill_pkg => $tax_item,
+ tax_cust_bill_pkg => $tax_item,
taxtype => 'FS::tax_rate',
taxnum => $tax_rate->taxnum,
taxratelocationnum => $tax_rate_location->taxratelocationnum,