summaryrefslogtreecommitdiff
path: root/FS/FS
diff options
context:
space:
mode:
authorjeff <jeff>2008-04-15 20:47:59 +0000
committerjeff <jeff>2008-04-15 20:47:59 +0000
commit6a24254d490f3d023728044daba0765f20f6971e (patch)
treec486026468a4e33092ae54925ff19b8e5dc7411b /FS/FS
parentbdbfd5c5a3bb7bc193b82dc39b98ae9ffe99da44 (diff)
(finally) wrap up new tax rate engine (for now)
Diffstat (limited to 'FS/FS')
-rw-r--r--FS/FS/cust_main.pm10
-rw-r--r--FS/FS/cust_tax_location.pm71
-rw-r--r--FS/FS/part_pkg.pm4
-rw-r--r--FS/FS/part_pkg_taxproduct.pm14
-rw-r--r--FS/FS/part_pkg_taxrate.pm66
-rw-r--r--FS/FS/tax_class.pm155
-rw-r--r--FS/FS/tax_rate.pm285
7 files changed, 565 insertions, 40 deletions
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 3490e46..168c43d 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -4828,15 +4828,15 @@ sub country_full {
code2country($self->country);
}
-=item geocode DATA_PROVIDER
+=item geocode DATA_VENDOR
-Returns a value for the customer location as encoded by DATA_PROVIDER.
-Currently this only makes sense for "CCH" as DATA_PROVIDER.
+Returns a value for the customer location as encoded by DATA_VENDOR.
+Currently this only makes sense for "CCH" as DATA_VENDOR.
=cut
sub geocode {
- my ($self, $data_provider) = (shift, shift); #always cch for now
+ my ($self, $data_vendor) = (shift, shift); #always cch for now
my $prefix = ( $conf->exists('tax-ship_address') && length($self->ship_last) )
? 'ship_'
@@ -4852,7 +4852,7 @@ sub geocode {
my $cust_tax_location =
qsearchs( {
'table' => 'cust_tax_location',
- 'hashref' => { 'zip' => $zip, 'data_provider' => $data_provider },
+ 'hashref' => { 'zip' => $zip, 'data_vendor' => $data_vendor },
'extra_sql' => $extra_sql,
}
);
diff --git a/FS/FS/cust_tax_location.pm b/FS/FS/cust_tax_location.pm
index 11faa3f..66d32a5 100644
--- a/FS/FS/cust_tax_location.pm
+++ b/FS/FS/cust_tax_location.pm
@@ -126,14 +126,54 @@ sub check {
sub batch_import {
- my $param = shift;
+ my ($param, $job) = @_;
my $fh = $param->{filehandle};
my $format = $param->{'format'};
+ my $imported = 0;
my @fields;
- if ( $format eq 'cch' ) {
+ my $hook;
+
+ my $line;
+ my ( $count, $last, $min_sec ) = (0, time, 5); #progressbar
+ if ( $job ) {
+ $count++
+ while ( defined($line=<$fh>) );
+ seek $fh, 0, 0;
+ }
+
+ if ( $format eq 'cch' || $format eq 'cch-update' ) {
@fields = qw( zip state plus4lo plus4hi geocode default );
+ push @fields, 'actionflag' if $format eq 'cch-update';
+
+ $imported++ if $format eq 'cch-update'; #empty file ok
+
+ $hook = sub {
+ my $hash = shift;
+
+ $hash->{'data_vendor'} = 'cch';
+
+ if (exists($hash->{actionflag}) && $hash->{actionflag} eq 'D') {
+ delete($hash->{actionflag});
+
+ my $cust_tax_location = qsearchs('cust_tax_location', $hash);
+ return "Can't find cust_tax_location to delete: ".
+ join(" ", map { "$_ => ". $hash->{$_} } @fields)
+ unless $cust_tax_location;
+
+ my $error = $cust_tax_location->delete;
+ return $error if $error;
+
+ delete($hash->{$_}) foreach (keys %$hash);
+ }
+
+ delete($hash->{'actionflag'});
+
+ '';
+
+ };
+
} elsif ( $format eq 'extended' ) {
die "unimplemented\n";
@fields = qw( );
@@ -146,8 +186,6 @@ sub batch_import {
my $csv = new Text::CSV_XS;
- my $imported = 0;
-
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
local $SIG{QUIT} = 'IGNORE';
@@ -159,22 +197,43 @@ sub batch_import {
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
- my $line;
while ( defined($line=<$fh>) ) {
$csv->parse($line) or do {
$dbh->rollback if $oldAutoCommit;
return "can't parse: ". $csv->error_input();
};
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count )
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
my @columns = $csv->fields();
my %cust_tax_location = ( 'data_vendor' => $format );;
foreach my $field ( @fields ) {
$cust_tax_location{$field} = shift @columns;
}
+ if ( scalar( @columns ) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Unexpected trailing columns in line (wrong format?): $line";
+ }
+
+ my $error = &{$hook}(\%cust_tax_location);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ next unless scalar(keys %cust_tax_location);
my $cust_tax_location = new FS::cust_tax_location( \%cust_tax_location );
- my $error = $cust_tax_location->insert;
+ $error = $cust_tax_location->insert;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm
index cffdc88..d4570f7 100644
--- a/FS/FS/part_pkg.pm
+++ b/FS/FS/part_pkg.pm
@@ -749,7 +749,7 @@ specified by GEOCODE (see L<FS::part_pkg_taxrate> and ).
sub part_pkg_taxrate {
my $self = shift;
- my ($data_provider, $geocode) = @_;
+ my ($data_vendor, $geocode) = @_;
my $dbh = dbh;
# CCH oddness in m2m
@@ -763,7 +763,7 @@ sub part_pkg_taxrate {
qsearch( { 'table' => 'part_pkg_taxrate',
'select' => 'distinct on(taxclassnum) *',
- 'hashref' => { 'data_provider' => $data_provider,
+ 'hashref' => { 'data_vendor' => $data_vendor,
'taxproductnum' => $self->taxproductnum,
},
'extra_sql' => $extra_sql,
diff --git a/FS/FS/part_pkg_taxproduct.pm b/FS/FS/part_pkg_taxproduct.pm
index 000d0d4..c66fb8c 100644
--- a/FS/FS/part_pkg_taxproduct.pm
+++ b/FS/FS/part_pkg_taxproduct.pm
@@ -2,7 +2,7 @@ package FS::part_pkg_taxproduct;
use strict;
use vars qw( @ISA );
-use FS::Record;
+use FS::Record qw( qsearch );
@ISA = qw(FS::Record);
@@ -79,6 +79,18 @@ Delete this record from the database.
=cut
+sub delete {
+ my $self = shift;
+
+ return "Can't delete a tax product which has attached package tax rates!"
+ if qsearch( 'part_pkg_taxrate', { 'taxproductnum' => $self->taxproductnum } );
+
+ return "Can't delete a tax product which has attached packages!"
+ if qsearch( 'part_pkg', { 'taxproductnum' => $self->taxproductnum } );
+
+ $self->SUPER::delete(@_);
+}
+
=item replace OLD_RECORD
Replaces the OLD_RECORD with this one in the database. If there is an error,
diff --git a/FS/FS/part_pkg_taxrate.pm b/FS/FS/part_pkg_taxrate.pm
index 3e7e7bd..5ef887d 100644
--- a/FS/FS/part_pkg_taxrate.pm
+++ b/FS/FS/part_pkg_taxrate.pm
@@ -166,17 +166,30 @@ an error, returns the error, otherwise returns false.
=cut
sub batch_import {
- my $param = shift;
+ my ($param, $job) = @_;
my $fh = $param->{filehandle};
my $format = $param->{'format'};
+ my $imported = 0;
my @fields;
my $hook;
- if ( $format eq 'cch' ) {
+
+ my $line;
+ my ( $count, $last, $min_sec ) = (0, time, 5); #progressbar
+ if ( $job ) {
+ $count++
+ while ( defined($line=<$fh>) );
+ seek $fh, 0, 0;
+ }
+
+ if ( $format eq 'cch' || $format eq 'cch-update' ) {
@fields = qw( city county state local geocode group groupdesc item
itemdesc provider customer taxtypetaxed taxcattaxed
taxable taxtype taxcat effdate rectype );
+ push @fields, 'actionflag' if $format eq 'cch-update';
+
+ $imported++ if $format eq 'cch-update'; #empty file ok
$hook = sub {
my $hash = shift;
@@ -186,6 +199,8 @@ sub batch_import {
return;
}
+ $hash->{'data_vendor'} = 'cch';
+
my %providers = ( '00' => 'Regulated LEC',
'01' => 'Regulated IXC',
'02' => 'Unregulated LEC',
@@ -213,6 +228,10 @@ sub batch_import {
);
unless ($part_pkg_taxproduct) {
+ return "Can't find part_pkg_taxproduct for txmatrix deletion: ".
+ join(" ", map { "$_ => ". $hash->{$_} } @fields)
+ if $hash->{'actionflag'} eq 'D';
+
$part_pkg_taxproduct{'description'} =
join(' : ', (map{ $hash->{$_} } qw(groupdesc itemdesc)),
$providers{$hash->{'provider'}},
@@ -234,15 +253,20 @@ sub batch_import {
);
for my $item (keys %map) {
+ my $class = join(':', map($hash->{$_}, @{$map{$item}}));
my $tax_class =
qsearchs( 'tax_class',
{ data_vendor => 'cch',
- 'taxclass' => join(':', map($hash->{$_}, @{$map{$item}})),
+ 'taxclass' => $class,
}
);
$hash->{$item} = $tax_class->taxclassnum
if $tax_class;
+ return "Can't find tax class for txmatrix deletion: ".
+ join(" ", map { "$_ => ". $hash->{$_} } @fields)
+ if ($hash->{'actionflag'} eq 'D' && !$tax_class && $class ne ':');
+
delete($hash->{$_}) foreach @{$map{$item}};
}
@@ -253,6 +277,23 @@ sub batch_import {
delete($hash->{'taxable'}) if ($hash->{'taxable'} eq 'N');
+ if (exists($hash->{actionflag}) && $hash->{actionflag} eq 'D') {
+ delete($hash->{actionflag});
+
+ my $part_pkg_taxrate = qsearchs('part_pkg_taxrate', $hash);
+ return "Can't find part_pkg_taxrate to delete: ".
+ #join(" ", map { "$_ => ". $hash->{$_} } @fields)
+ join(" ", map { "$_ => *". $hash->{$_}. '*' } keys(%$hash) )
+ unless $part_pkg_taxrate;
+
+ my $error = $part_pkg_taxrate->delete;
+ return $error if $error;
+
+ delete($hash->{$_}) foreach (keys %$hash);
+ }
+
+ delete($hash->{actionflag});
+
'';
};
@@ -269,8 +310,6 @@ sub batch_import {
my $csv = new Text::CSV_XS;
- my $imported = 0;
-
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
local $SIG{QUIT} = 'IGNORE';
@@ -282,19 +321,34 @@ sub batch_import {
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
- my $line;
while ( defined($line=<$fh>) ) {
$csv->parse($line) or do {
$dbh->rollback if $oldAutoCommit;
return "can't parse: ". $csv->error_input();
};
+
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count )
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
my @columns = $csv->fields();
my %part_pkg_taxrate = ( 'data_vendor' => $format );
foreach my $field ( @fields ) {
$part_pkg_taxrate{$field} = shift @columns;
}
+ if ( scalar( @columns ) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Unexpected trailing columns in line (wrong format?): $line";
+ }
+
my $error = &{$hook}(\%part_pkg_taxrate);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
diff --git a/FS/FS/tax_class.pm b/FS/FS/tax_class.pm
index 0a939ad..ed63939 100644
--- a/FS/FS/tax_class.pm
+++ b/FS/FS/tax_class.pm
@@ -79,6 +79,25 @@ Delete this record from the database.
=cut
+sub delete {
+ my $self = shift;
+
+ return "Can't delete a tax class which has tax rates!"
+ if qsearch( 'tax_rate', { 'taxclassnum' => $self->taxclassnum } );
+
+ return "Can't delete a tax class which has package tax rates!"
+ if qsearch( 'part_pkg_taxrate', { 'taxclassnum' => $self->taxclassnum } );
+
+ return "Can't delete a tax class which has package tax rates!"
+ if qsearch( 'part_pkg_taxrate', { 'taxclassnumtaxed' => $self->taxclassnum } );
+
+ return "Can't delete a tax class which has package tax overrides!"
+ if qsearch( 'part_pkg_taxoverride', { 'taxclassnum' => $self->taxclassnum } );
+
+ $self->SUPER::delete(@_);
+
+}
+
=item replace OLD_RECORD
Replaces the OLD_RECORD with this one in the database. If there is an error,
@@ -116,7 +135,7 @@ an error, returns the error, otherwise returns false.
=cut
sub batch_import {
- my $param = shift;
+ my ($param, $job) = @_;
my $fh = $param->{filehandle};
my $format = $param->{'format'};
@@ -126,31 +145,106 @@ sub batch_import {
my $endhook;
my $data = {};
my $imported = 0;
+ my $dbh = dbh;
- if ( $format eq 'cch' ) {
+ my $line;
+ my ( $count, $last, $min_sec ) = (0, time, 5); #progressbar
+ if ( $job ) {
+ $count++
+ while ( defined($line=<$fh>) );
+ seek $fh, 0, 0;
+ }
+
+ if ( $format eq 'cch' || $format eq 'cch-update' ) {
@fields = qw( table name pos number length value description );
+ push @fields, 'actionflag' if $format eq 'cch-update';
$hook = sub {
my $hash = shift;
if ($hash->{'table'} eq 'DETAIL') {
push @{$data->{'taxcat'}}, [ $hash->{'value'}, $hash->{'description'} ]
- if $hash->{'name'} eq 'TAXCAT';
+ if ($hash->{'name'} eq 'TAXCAT' &&
+ (!exists($hash->{actionflag}) || $hash->{actionflag} eq 'I') );
push @{$data->{'taxtype'}}, [ $hash->{'value'}, $hash->{'description'} ]
- if $hash->{'name'} eq 'TAXTYPE';
+ if ($hash->{'name'} eq 'TAXTYPE' &&
+ (!exists($hash->{actionflag}) || $hash->{actionflag} eq 'I') );
+
+ if (exists($hash->{actionflag}) && $hash->{actionflag} eq 'D') {
+ my $name = $hash->{'name'};
+ my $value = $hash->{'value'};
+ return "Bad value for $name: $value"
+ unless $value =~ /^\d+$/;
+
+ if ($name eq 'TAXCAT' || $name eq 'TAXTYPE') {
+ my @tax_class = qsearch( 'tax_class',
+ { 'data_vendor' => 'cch' },
+ '',
+ "AND taxclass LIKE '".
+ ($name eq 'TAXTYPE' ? $value : '%').":".
+ ($name eq 'TAXCAT' ? $value : '%')."'",
+ );
+ foreach (@tax_class) {
+ my $error = $_->delete;
+ return $error if $error;
+ }
+ }
+ }
+
}
delete($hash->{$_})
for qw( data_vendor table name pos number length value description );
+ delete($hash->{actionflag}) if exists($hash->{actionflag});
'';
};
$endhook = sub {
- foreach my $type (@{$data->{'taxtype'}}) {
+
+ my $sql = "SELECT DISTINCT ".
+ "substring(taxclass from 1 for position(':' in taxclass)-1),".
+ "substring(description from 1 for position(':' in description)-1) ".
+ "FROM tax_class WHERE data_vendor='cch'";
+
+ my $sth = $dbh->prepare($sql) or die $dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ my @old_types = @{$sth->fetchall_arrayref};
+
+ $sql = "SELECT DISTINCT ".
+ "substring(taxclass from position(':' in taxclass)+1),".
+ "substring(description from position(':' in description)+1) ".
+ "FROM tax_class WHERE data_vendor='cch'";
+
+ $sth = $dbh->prepare($sql) or die $dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ my @old_cats = @{$sth->fetchall_arrayref};
+
+ my $catcount = exists($data->{'taxcat'}) ? scalar(@{$data->{'taxcat'}})
+ : 0;
+ my $typecount = exists($data->{'taxtype'}) ? scalar(@{$data->{'taxtype'}})
+ : 0;
+
+ my $count = scalar(@old_types) * $catcount
+ + $typecount * (scalar(@old_cats) + $catcount);
+
+ $imported = 1 if $format eq 'cch-update'; #empty file ok
+
+ foreach my $type (@old_types) {
foreach my $cat (@{$data->{'taxcat'}}) {
+
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count )
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
my $tax_class =
new FS::tax_class( { 'data_vendor' => 'cch',
'taxclass' => $type->[0].':'.$cat->[0],
@@ -161,6 +255,31 @@ sub batch_import {
$imported++;
}
}
+
+ foreach my $type (@{$data->{'taxtype'}}) {
+ foreach my $cat (@old_cats, @{$data->{'taxcat'}}) {
+
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count )
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
+ my $tax_class =
+ new FS::tax_class( { 'data_vendor' => 'cch',
+ 'taxclass' => $type->[0].':'.$cat->[0],
+ 'description' => $type->[1].':'.$cat->[1],
+ } );
+ my $error = $tax_class->insert;
+ return $error if $error;
+ $imported++;
+ }
+ }
+
'';
};
@@ -186,10 +305,19 @@ sub batch_import {
my $oldAutoCommit = $FS::UID::AutoCommit;
local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
- my $line;
while ( defined($line=<$fh>) ) {
+
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count )
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
$csv->parse($line) or do {
$dbh->rollback if $oldAutoCommit;
return "can't parse: ". $csv->error_input();
@@ -201,16 +329,21 @@ sub batch_import {
foreach my $field ( @fields ) {
$tax_class{$field} = shift @columns;
}
+ if ( scalar( @columns ) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Unexpected trailing columns in line (wrong format?): $line";
+ }
+
my $error = &{$hook}(\%tax_class);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
+
next unless scalar(keys %tax_class);
my $tax_class = new FS::tax_class( \%tax_class );
$error = $tax_class->insert;
-
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return "can't insert tax_class for $line: $error";
@@ -227,17 +360,19 @@ sub batch_import {
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
- return "Empty file!" unless $imported;
+ return "Empty File!" unless $imported;
''; #no error
}
-
=back
=head1 BUGS
+ batch_import does not handle mixed I and D records in the same file for
+ format cch-update
+
=head1 SEE ALSO
L<FS::Record>, schema.html from the base documentation.
diff --git a/FS/FS/tax_rate.pm b/FS/FS/tax_rate.pm
index 3d56a0d..268edca 100644
--- a/FS/FS/tax_rate.pm
+++ b/FS/FS/tax_rate.pm
@@ -5,9 +5,13 @@ use vars qw( @ISA $DEBUG $me
%tax_unittypes %tax_maxtypes %tax_basetypes %tax_authorities
%tax_passtypes );
use Date::Parse;
+use Storable qw( thaw );
+use MIME::Base64;
use FS::Record qw( qsearchs dbh );
use FS::tax_class;
use FS::cust_bill_pkg;
+use FS::cust_tax_location;
+use FS::part_pkg_taxrate;
@ISA = qw( FS::Record );
@@ -410,21 +414,38 @@ sub taxline {
=cut
sub batch_import {
- my $param = shift;
+ my ($param, $job) = @_;
my $fh = $param->{filehandle};
my $format = $param->{'format'};
+ my %insert = ();
+ my %delete = ();
+
my @fields;
my $hook;
- if ( $format eq 'cch' ) {
+
+ my $line;
+ my ( $count, $last, $min_sec ) = (0, time, 5); #progressbar
+ if ( $job ) {
+ $count++
+ while ( defined($line=<$fh>) );
+ seek $fh, 0, 0;
+ }
+ $count *=2;
+
+ if ( $format eq 'cch' || $format eq 'cch-update' ) {
@fields = qw( geocode inoutcity inoutlocal tax location taxbase taxmax
excessrate effective_date taxauth taxtype taxcat taxname
usetax useexcessrate fee unittype feemax maxtype passflag
passtype basetype );
+ push @fields, 'actionflag' if $format eq 'cch-update';
+
$hook = sub {
my $hash = shift;
+ $hash->{'actionflag'} ='I' if ($hash->{'data_vendor'} eq 'cch');
+ $hash->{'data_vendor'} ='cch';
$hash->{'effective_date'} = str2time($hash->{'effective_date'});
my $taxclassid =
@@ -435,7 +456,7 @@ sub batch_import {
);
my $tax_class = qsearchs( 'tax_class', \%tax_class );
- return "Error inserting tax rate: no tax class $taxclassid"
+ return "Error updating tax rate: no tax class $taxclassid"
unless $tax_class;
$hash->{'taxclassnum'} = $tax_class->taxclassnum;
@@ -456,6 +477,15 @@ sub batch_import {
if length($hash->{$_}) > 80;
}
+ my $actionflag = delete($hash->{'actionflag'});
+ if ($actionflag eq 'I') {
+ $insert{ $hash->{'geocode'}. ':'. $hash->{'taxclassnum'} } = $hash;
+ }elsif ($actionflag eq 'D') {
+ $delete{ $hash->{'geocode'}. ':'. $hash->{'taxclassnum'} } = $hash;
+ }else{
+ return "Unexpected action flag: ". $hash->{'actionflag'};
+ }
+
'';
};
@@ -486,15 +516,21 @@ sub batch_import {
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
- my $line;
while ( defined($line=<$fh>) ) {
$csv->parse($line) or do {
$dbh->rollback if $oldAutoCommit;
return "can't parse: ". $csv->error_input();
};
- warn "$me batch_import: $imported\n"
- if (!($imported % 100) && $DEBUG);
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count )
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
my @columns = $csv->fields();
@@ -502,14 +538,95 @@ sub batch_import {
foreach my $field ( @fields ) {
$tax_rate{$field} = shift @columns;
}
+ if ( scalar( @columns ) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Unexpected trailing columns in line (wrong format?): $line";
+ }
+
my $error = &{$hook}(\%tax_rate);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
- my $tax_rate = new FS::tax_rate( \%tax_rate );
- $error = $tax_rate->insert;
+ $imported++;
+
+ }
+
+ for (grep { !exists($delete{$_}) } keys %insert) {
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count )
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
+ my $tax_rate = new FS::tax_rate( $insert{$_} );
+ my $error = $tax_rate->insert;
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't insert tax_rate for $line: $error";
+ }
+
+ $imported++;
+ }
+
+ for (grep { exists($delete{$_}) } keys %insert) {
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count )
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
+ my $old = qsearchs( 'tax_rate', $delete{$_} );
+ unless ($old) {
+ $dbh->rollback if $oldAutoCommit;
+ $old = $delete{$_};
+ return "can't find tax_rate to replace for: ".
+ #join(" ", map { "$_ => ". $old->{$_} } @fields);
+ join(" ", map { "$_ => ". $old->{$_} } keys(%$old) );
+ }
+ my $new = new FS::tax_rate( $insert{$_} );
+ $new->taxnum($old->taxnum);
+ my $error = $new->replace($old);
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't insert tax_rate for $line: $error";
+ }
+
+ $imported++;
+ $imported++;
+ }
+
+ for (grep { !exists($insert{$_}) } keys %delete) {
+ if ( $job ) { # progress bar
+ if ( time - $min_sec > $last ) {
+ my $error = $job->update_statustext(
+ int( 100 * $imported / $count )
+ );
+ die $error if $error;
+ $last = time;
+ }
+ }
+
+ my $tax_rate = qsearchs( 'tax_rate', $delete{$_} );
+ unless ($tax_rate) {
+ $dbh->rollback if $oldAutoCommit;
+ $tax_rate = $delete{$_};
+ return "can't find tax_rate to delete for: ".
+ #join(" ", map { "$_ => ". $tax_rate->{$_} } @fields);
+ join(" ", map { "$_ => ". $tax_rate->{$_} } keys(%$tax_rate) );
+ }
+ my $error = $tax_rate->delete;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
@@ -527,12 +644,160 @@ sub batch_import {
}
+=item process_batch
+
+Load an batch import as a queued JSRPC job
+
+=cut
+
+sub process_batch {
+ my $job = shift;
+
+ my $param = thaw(decode_base64(shift));
+ my $format = $param->{'format'}; #well... this is all cch specific
+
+ my $files = $param->{'uploaded_files'}
+ or die "No files provided.";
+
+ my (%files) = map { /^(\w+):([\.\w]+)$/ ? ($1,$2):() } split /,/, $files;
+
+ if ($format eq 'cch') {
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+ my $error = '';
+
+ my @list = ( 'CODE', 'codefile', \&FS::tax_class::batch_import,
+ 'PLUS4', 'plus4file', \&FS::cust_tax_location::batch_import,
+ 'TXMATRIX', 'txmatrix', \&FS::part_pkg_taxrate::batch_import,
+ 'DETAIL', 'detail', \&FS::tax_rate::batch_import,
+ );
+ while( scalar(@list) ) {
+ my ($name, $file, $import_sub) = (shift @list, shift @list, shift @list);
+ unless ($files{$file}) {
+ $error = "No $name supplied";
+ next;
+ }
+ my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+ my $filename = "$dir/". $files{$file};
+ open my $fh, "< $filename" or $error ||= "Can't open $name file: $!";
+
+ $error ||= &{$import_sub}({ 'filehandle' => $fh, 'format' => $format }, $job);
+ close $fh;
+ unlink $filename or warn "Can't delete $filename: $!";
+ }
+
+ if ($error) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ die $error;
+ }else{
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ }
+
+ }elsif ($format eq 'cch-update') {
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+ my $error = '';
+ my @insert_list = ();
+ my @delete_list = ();
+
+ my @list = ( 'CODE', 'codefile', \&FS::tax_class::batch_import,
+ 'PLUS4', 'plus4file', \&FS::cust_tax_location::batch_import,
+ 'TXMATRIX', 'txmatrix', \&FS::part_pkg_taxrate::batch_import,
+ );
+ my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+ while( scalar(@list) ) {
+ my ($name, $file, $import_sub) = (shift @list, shift @list, shift @list);
+ unless ($files{$file}) {
+ $error = "No $name supplied";
+ next;
+ }
+ my $filename = "$dir/". $files{$file};
+ open my $fh, "< $filename" or $error ||= "Can't open $name file $filename: $!";
+ unlink $filename or warn "Can't delete $filename: $!";
+
+ my $ifh = new File::Temp( TEMPLATE => "$name.insert.XXXXXXXX",
+ DIR => $dir,
+ UNLINK => 0, #meh
+ ) or die "can't open temp file: $!\n";
+
+ my $dfh = new File::Temp( TEMPLATE => "$name.delete.XXXXXXXX",
+ DIR => $dir,
+ UNLINK => 0, #meh
+ ) or die "can't open temp file: $!\n";
+
+ while(<$fh>) {
+ my $handle = '';
+ $handle = $ifh if $_ =~ /"I"\s*$/;
+ $handle = $dfh if $_ =~ /"D"\s*$/;
+ unless ($handle) {
+ $error = "bad input line: $_" unless $handle;
+ last;
+ }
+ print $handle $_;
+ }
+ close $fh;
+ close $ifh;
+ close $dfh;
+
+ push @insert_list, $name, $ifh->filename, $import_sub;
+ unshift @delete_list, $name, $dfh->filename, $import_sub;
+
+ }
+ while( scalar(@insert_list) ) {
+ my ($name, $file, $import_sub) =
+ (shift @insert_list, shift @insert_list, shift @insert_list);
+
+ open my $fh, "< $file" or $error ||= "Can't open $name file $file: $!";
+ $error ||=
+ &{$import_sub}({ 'filehandle' => $fh, 'format' => $format }, $job);
+ close $fh;
+ unlink $file or warn "Can't delete $file: $!";
+ }
+
+ $error = "No DETAIL supplied"
+ unless ($files{detail});
+ open my $fh, "< $dir/". $files{detail}
+ or $error ||= "Can't open DETAIL file: $!";
+ $error ||=
+ &FS::tax_rate::batch_import({ 'filehandle' => $fh, 'format' => $format },
+ $job);
+ close $fh;
+ unlink "$dir/". $files{detail} or warn "Can't delete $files{detail}: $!"
+ if $files{detail};
+
+ while( scalar(@delete_list) ) {
+ my ($name, $file, $import_sub) =
+ (shift @delete_list, shift @delete_list, shift @delete_list);
+
+ open my $fh, "< $file" or $error ||= "Can't open $name file $file: $!";
+ $error ||=
+ &{$import_sub}({ 'filehandle' => $fh, 'format' => $format }, $job);
+ close $fh;
+ unlink $file or warn "Can't delete $file: $!";
+ }
+
+ if ($error) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ die $error;
+ }else{
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ }
+
+ }else{
+ die "Unknown format: $format";
+ }
+
+}
+
=back
=head1 BUGS
-regionselector? putting web ui components in here? they should probably live
-somewhere else...
+ Mixing automatic and manual editing works poorly at present.
=head1 SEE ALSO