use strict;
use base qw( FS::Record );
use FS::Record qw( qsearch qsearchs dbh );
+use FS::Misc qw( csv_from_fixed );
=head1 NAME
=head1 DESCRIPTION
-An FS::tax_rate_location object represents an example. FS::tax_rate_location inherits from
-FS::Record. The following fields are currently supported:
+An FS::tax_rate_location object represents a tax jurisdiction. The only
+functional field is "geocode", a foreign key to tax rates (L<FS::tax_rate>)
+that apply in the jurisdiction. The city, county, state, and country fields
+are provided for description and reporting.
-=over 4
-
-=item taxratelocationnum
-
-Primary key (assigned automatically for new tax_rate_locations)
-
-=item data_vendor
-
-The tax data vendor
-
-=item geocode
+FS::tax_rate_location inherits from FS::Record. The following fields are
+currently supported:
-A unique geographic location code provided by the data vendor
-
-=item city
+=over 4
-City
+=item taxratelocationnum - Primary key (assigned automatically for new
+tax_rate_locations)
-=item county
+=item data_vendor - The tax data vendor ('cch' or 'billsoft').
-County
+=item geocode - A unique geographic location code provided by the data vendor
-=item state
+=item city - City
-State
+=item county - County
-=item disabled
+=item state - State (2-letter code)
-If 'Y' this record is no longer active.
+=item country - Country (2-letter code, optional)
+=item disabled - If 'Y' this record is no longer active.
=back
;
return $error if $error;
- my $t = qsearchs( 'tax_rate_location',
- { map { $_ => $self->$_ } qw( data_vendor geocode ) },
- );
+ my $t;
+ $t = qsearchs( 'tax_rate_location',
+ { disabled => '',
+ ( map { $_ => $self->$_ } qw( data_vendor geocode ) ),
+ },
+ )
+ unless $self->disabled;
+
+ $t = $self->by_key( $self->taxratelocationnum )
+ if ( !$t && $self->taxratelocationnum );
- return "geocode already in use for this vendor"
+ return "geocode ". $self->geocode. " already in use for this vendor"
if ( $t && $t->taxratelocationnum != $self->taxratelocationnum );
return "may only be disabled"
$self->SUPER::check;
}
+=item find_or_insert
+
+Finds an existing, non-disabled tax jurisdiction matching the data_vendor
+and geocode fields. If there is one, updates its city, county, state, and
+country to match this record. If there is no existing record, inserts this
+record.
+
+=cut
+
+sub find_or_insert {
+ my $self = shift;
+ my $existing = qsearchs('tax_rate_location', {
+ disabled => '',
+ data_vendor => $self->data_vendor,
+ geocode => $self->geocode
+ });
+ if ($existing) {
+ my $update = 0;
+ foreach (qw(city county state country)) {
+ if ($self->get($_) ne $existing->get($_)) {
+ $update++;
+ }
+ }
+ $self->set(taxratelocationnum => $existing->taxratelocationnum);
+ if ($update) {
+ return $self->replace($existing);
+ } else {
+ return;
+ }
+ } else {
+ return $self->insert;
+ }
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=item location_sql KEY => VALUE, ...
+
+Returns an SQL fragment identifying matching tax_rate_location /
+cust_bill_pkg_tax_rate_location records.
+
+Parameters are county, state, city and locationtaxid
+
+=cut
+
+sub location_sql {
+ my($class, %param) = @_;
+
+ my %pn = (
+ 'city' => 'tax_rate_location.city',
+ 'county' => 'tax_rate_location.county',
+ 'state' => 'tax_rate_location.state',
+ 'locationtaxid' => 'cust_bill_pkg_tax_rate_location.locationtaxid',
+ );
+
+ my %ph = map { $pn{$_} => dbh->quote($param{$_}) } keys %pn;
+
+ join( ' AND ',
+ map { "( $_ = $ph{$_} OR $ph{$_} = '' AND $_ IS NULL)" } keys %ph
+ );
+
+}
+
=back
=head1 SUBROUTINES
=over 4
-=item batch_import
+=item batch_import HASHREF, JOB
+
+Starts importing tax_rate_location records from a file. HASHREF must contain
+'filehandle' (an open handle to the input file) and 'format' (one of 'cch',
+'cch-fixed', 'cch-update', 'cch-fixed-update', or 'billsoft'). JOB is an
+L<FS::queue> object to receive progress messages.
=cut
+# XXX move this into TaxEngine modules at some point
+
sub batch_import {
my ($param, $job) = @_;
my $line;
my ( $count, $last, $min_sec ) = (0, time, 5); #progressbar
- if ( $job || scalar(@column_callbacks) ) {
+ if ( $job || scalar(@column_callbacks) ) { # this makes zero sense
my $error =
csv_from_fixed(\$fh, \$count, \@column_lengths, \@column_callbacks);
return $error if $error;
if (exists($hash->{'actionflag'}) && $hash->{'actionflag'} eq 'D') {
delete($hash->{actionflag});
- $hash->{deleted} = '';
+ $hash->{disabled} = '';
my $tax_rate_location = qsearchs('tax_rate_location', $hash);
return "Can't find tax_rate_location to delete: ".
join(" ", map { "$_ => ". $hash->{$_} } @fields)
};
+ } elsif ( $format eq 'billsoft' ) {
+ @fields = ( qw( geocode alt_location country state county city ), '', '' );
+
+ $hook = sub {
+ my $hash = shift;
+ if ($hash->{alt_location}) {
+ # don't import these; the jurisdiction should be named using its
+ # primary city
+ %$hash = ();
+ return;
+ }
+
+ $hash->{data_vendor} = 'billsoft';
+ # unlike cust_tax_location, keep the whole-country and whole-state
+ # rows, but strip the whitespace
+ $hash->{county} =~ s/^ //g;
+ $hash->{state} =~ s/^ //g;
+ $hash->{country} =~ s/^ //g;
+ $hash->{city} =~ s/[^\w ]//g; # remove asterisks and other bad things
+ $hash->{country} = substr($hash->{country}, 0, 2);
+ '';
+ }
+
} elsif ( $format eq 'extended' ) {
die "unimplemented\n";
@fields = qw( );
if ( $job ) { # progress bar
if ( time - $min_sec > $last ) {
my $error = $job->update_statustext(
- int( 100 * $imported / $count )
+ int( 100 * $imported / $count ) .
+ ',Creating tax jurisdiction records'
);
die $error if $error;
$last = time;
}
if ( scalar( @columns ) ) {
$dbh->rollback if $oldAutoCommit;
- return "Unexpected trailing columns in line (wrong format?): $line";
+ return "Unexpected trailing columns in line (wrong format?) importing tax-rate_location: $line";
}
my $error = &{$hook}(\%tax_rate_location);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
- return "can't insert tax_rate for $line: $error";
+ return "can't insert tax_rate_location for $line: $error";
}
}