+sub _remember_tax_products {
+ my ( $job, $format, $taxproduct ) = @_;
+
+ # XXX FIXME this loop only works when cch is the only data provider
+
+ my ( $imported, $last, $min_sec ) = _progressbar_foo();
+
+ my $extra_sql = "WHERE taxproductnum IS NOT NULL OR ".
+ "0 < ( SELECT count(*) from part_pkg_option WHERE ".
+ " part_pkg_option.pkgpart = part_pkg.pkgpart AND ".
+ " optionname LIKE 'usage_taxproductnum_%' AND ".
+ " optionvalue != '' )";
+ my @items = qsearch( { table => 'part_pkg',
+ select => 'DISTINCT pkgpart,taxproductnum',
+ hashref => {},
+ extra_sql => $extra_sql,
+ }
+ );
+ my $count = scalar(@items);
+ foreach my $part_pkg ( @items ) {
+ if ( time - $min_sec > $last ) {
+ $job->update_statustext(
+ int( 100 * $imported / $count ). ",Remembering tax products"
+ );
+ $last = time;
+ }
+ $imported++;
+ warn "working with package part ". $part_pkg->pkgpart.
+ "which has a taxproductnum of ". $part_pkg->taxproductnum. "\n" if $DEBUG;
+ my $part_pkg_taxproduct = $part_pkg->taxproduct('');
+ $taxproduct->{$part_pkg->pkgpart}->{''} = $part_pkg_taxproduct->taxproduct
+ if $part_pkg_taxproduct && $part_pkg_taxproduct->data_vendor eq $format;
+
+ foreach my $option ( $part_pkg->part_pkg_option ) {
+ next unless $option->optionname =~ /^usage_taxproductnum_(\w)$/;
+ my $class = $1;
+
+ $part_pkg_taxproduct = $part_pkg->taxproduct($class);
+ $taxproduct->{$part_pkg->pkgpart}->{$class} =
+ $part_pkg_taxproduct->taxproduct
+ if $part_pkg_taxproduct && $part_pkg_taxproduct->data_vendor eq $format;
+ }
+ }
+}
+
+sub _restore_remembered_tax_products {
+ my ( $job, $format, $taxproduct ) = @_;
+
+ # cch specific
+
+ my ( $imported, $last, $min_sec ) = _progressbar_foo();
+ my $count = scalar(keys %$taxproduct);
+ foreach my $pkgpart ( keys %$taxproduct ) {
+ warn "restoring taxproductnums on pkgpart $pkgpart\n" if $DEBUG;
+ if ( time - $min_sec > $last ) {
+ $job->update_statustext(
+ int( 100 * $imported / $count ). ",Restoring tax products"
+ );
+ $last = time;
+ }
+ $imported++;
+
+ my $part_pkg = qsearchs('part_pkg', { pkgpart => $pkgpart } );
+ unless ( $part_pkg ) {
+ return "somehow failed to find part_pkg with pkgpart $pkgpart!\n";
+ }
+
+ my %options = $part_pkg->options;
+ my %pkg_svc = map { $_->svcpart => $_->quantity } $part_pkg->pkg_svc;
+ my $primary_svc = $part_pkg->svcpart;
+ my $new = new FS::part_pkg { $part_pkg->hash };
+
+ foreach my $class ( keys %{ $taxproduct->{$pkgpart} } ) {
+ warn "working with class '$class'\n" if $DEBUG;
+ my $part_pkg_taxproduct =
+ qsearchs( 'part_pkg_taxproduct',
+ { taxproduct => $taxproduct->{$pkgpart}->{$class},
+ data_vendor => $format,
+ }
+ );
+
+ unless ( $part_pkg_taxproduct ) {
+ return "failed to find part_pkg_taxproduct (".
+ $taxproduct->{pkgpart}->{$class}. ") for pkgpart $pkgpart\n";
+ }
+
+ if ( $class eq '' ) {
+ $new->taxproductnum($part_pkg_taxproduct->taxproductnum);
+ next;
+ }
+
+ $options{"usage_taxproductnum_$class"} =
+ $part_pkg_taxproduct->taxproductnum;
+
+ }
+
+ my $error = $new->replace( $part_pkg,
+ 'pkg_svc' => \%pkg_svc,
+ 'primary_svc' => $primary_svc,
+ 'options' => \%options,
+ );
+
+ return $error if $error;
+
+ }
+
+ '';
+}
+
+sub _restore_remembered_disabled_taxes {
+ my ( $job, $format, $disabled_tax_rate ) = @_;
+
+ my ( $imported, $last, $min_sec ) = _progressbar_foo();
+ my $count = scalar(keys %$disabled_tax_rate);
+ foreach my $key (keys %$disabled_tax_rate) {
+ if ( time - $min_sec > $last ) {
+ $job->update_statustext(
+ int( 100 * $imported / $count ). ",Disabling tax rates"
+ );
+ $last = time;
+ }
+ $imported++;
+ my ($geocode,$taxclass) = split /:/, $key, 2;
+ my @tax_class = qsearch( 'tax_class', { data_vendor => $format,
+ taxclass => $taxclass,
+ } );
+ return "found multiple tax_class records for format $format class $taxclass"
+ if scalar(@tax_class) > 1;
+
+ unless (scalar(@tax_class)) {
+ warn "no tax_class for format $format class $taxclass\n";
+ next;
+ }
+
+ my @tax_rate =
+ qsearch('tax_rate', { data_vendor => $format,
+ geocode => $geocode,
+ taxclassnum => $tax_class[0]->taxclassnum,
+ }
+ );
+
+ if (scalar(@tax_rate) > 1) {
+ return "found multiple tax_rate records for format $format geocode ".
+ "$geocode and taxclass $taxclass ( taxclassnum ".
+ $tax_class[0]->taxclassnum. " )";
+ }
+
+ if (scalar(@tax_rate)) {
+ $tax_rate[0]->disabled('Y');
+ my $error = $tax_rate[0]->replace;
+ return $error if $error;
+ }
+ }
+}
+
+sub _remove_old_tax_data {
+ my ( $job, $format ) = @_;
+
+ my $dbh = dbh;
+ my $error = $job->update_statustext( "0,Removing old tax data" );
+ die $error if $error;
+
+ my $sql = "UPDATE public.tax_rate_location SET disabled='Y' ".
+ "WHERE data_vendor = ". $dbh->quote($format);
+ $dbh->do($sql) or return "Failed to execute $sql: ". $dbh->errstr;
+
+ my @table = qw(
+ tax_rate part_pkg_taxrate part_pkg_taxproduct tax_class cust_tax_location
+ );
+ foreach my $table ( @table ) {
+ $sql = "DELETE FROM public.$table WHERE data_vendor = ".
+ $dbh->quote($format);
+ $dbh->do($sql) or return "Failed to execute $sql: ". $dbh->errstr;
+ }
+
+ if ( $format eq 'cch' ) {
+ $sql = "DELETE FROM public.cust_tax_location WHERE data_vendor = ".
+ $dbh->quote("$format-zip");
+ $dbh->do($sql) or return "Failed to execute $sql: ". $dbh->errstr;
+ }
+
+ '';
+}
+
+sub _create_temporary_tables {
+ my ( $job, $format ) = @_;
+
+ my $dbh = dbh;
+ my $error = $job->update_statustext( "0,Creating temporary tables" );
+ die $error if $error;
+
+ my @table = qw( tax_rate
+ tax_rate_location
+ part_pkg_taxrate
+ part_pkg_taxproduct
+ tax_class
+ cust_tax_location
+ );
+ foreach my $table ( @table ) {
+ my $sql =
+ "CREATE TEMPORARY TABLE $table ( LIKE $table INCLUDING DEFAULTS )";
+ $dbh->do($sql) or return "Failed to execute $sql: ". $dbh->errstr;
+ }
+
+ '';
+}
+
+sub _copy_from_temp {
+ my ( $job, $format ) = @_;
+
+ my $dbh = dbh;
+ my $error = $job->update_statustext( "0,Making permanent" );
+ die $error if $error;
+
+ my @table = qw( tax_rate
+ tax_rate_location
+ part_pkg_taxrate
+ part_pkg_taxproduct
+ tax_class
+ cust_tax_location
+ );
+ foreach my $table ( @table ) {
+ my $sql =
+ "INSERT INTO public.$table SELECT * from $table";
+ $dbh->do($sql) or return "Failed to execute $sql: ". $dbh->errstr;