X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fcust_main_county.pm;h=958233440dfec67eca2ba9e6d7c03459e38704f3;hp=a8aaeef77fa27c2c207237fd1c640013819458e6;hb=5372897f367498972c96f5494e142e6e11b29eb8;hpb=1d9fd3b93be720823656cd23db79ff74e2e7a829 diff --git a/FS/FS/cust_main_county.pm b/FS/FS/cust_main_county.pm index a8aaeef77..958233440 100644 --- a/FS/FS/cust_main_county.pm +++ b/FS/FS/cust_main_county.pm @@ -3,7 +3,8 @@ use base qw( FS::Record ); use strict; use vars qw( @EXPORT_OK $conf - @cust_main_county %cust_main_county $countyflag $DEBUG $me); # $cityflag ); + @cust_main_county %cust_main_county $countyflag ); # $cityflag ); +use Carp qw( croak ); use Exporter; use FS::Record qw( qsearch qsearchs dbh ); use FS::cust_bill_pkg; @@ -12,11 +13,9 @@ use FS::cust_pkg; use FS::part_pkg; use FS::cust_tax_exempt; use FS::cust_tax_exempt_pkg; +use FS::Log; use FS::upgrade_journal; -$DEBUG = 0; -$me = '[FS::cust_main_county]'; - @EXPORT_OK = qw( regionselector ); @cust_main_county = (); @@ -563,6 +562,40 @@ sub taxline { return $tax_item; } +=head1 find_wa_tax_dupes + +Return a list of cust_main_county Record objects that are detected +as duplicate washington state sales tax rows (source=wa_state) +within their respective tax classes + +=cut + +sub find_wa_tax_dupes { + my %cust_main_county; + my @dupes; + + for my $row ( qsearch( cust_main_county => { source => 'wa_sales' } ) ) { + my $taxclass = $row->taxclass || 'none'; + $cust_main_county{$taxclass} ||= {}; + + my $district = $row->district || 'none'; + $cust_main_county{$taxclass}->{$district} ||= []; + + push @{ $cust_main_county{$taxclass}->{$district} }, $row; + } + + for my $taxclass ( keys %cust_main_county ) { + for my $district ( keys %{ $cust_main_county{$taxclass} } ) { + my $tax_rows = $cust_main_county{$taxclass}->{$district}; + if ( scalar @$tax_rows > 1 ) { + push @dupes, @$tax_rows; + } + } + } + + @dupes; +} + =back =head1 SUBROUTINES @@ -686,327 +719,75 @@ END } sub _merge_into { - # for internal use: takes another cust_main_county object, transfers - # all existing references to this record to that one, and deletes this - # one. - my $record = shift; - my $other = shift or die "record to merge into must be provided"; - my $new_taxnum = $other->taxnum; - my $old_taxnum = $record->taxnum; - if ($other->tax != $record->tax or - $other->exempt_amount != $record->exempt_amount) { - # don't assume these are the same. - warn "Found duplicate taxes (#$new_taxnum and #$old_taxnum) but they have different rates and can't be merged.\n"; - } else { - warn "Merging tax #$old_taxnum into #$new_taxnum\n"; - foreach my $table (qw( - cust_bill_pkg_tax_location - cust_bill_pkg_tax_location_void - cust_tax_exempt_pkg - cust_tax_exempt_pkg_void - )) { - foreach my $row (qsearch($table, { 'taxnum' => $old_taxnum })) { - $row->set('taxnum' => $new_taxnum); - my $error = $row->replace; - die $error if $error; - } - } - my $error = $record->delete; - die $error if $error; - } -} - -=item process_edit_import - -=cut - -use Data::Dumper; -sub process_edit_import { - my $job = shift; - - my $opt = { 'table' => 'cust_main_county', - 'params' => [], #required, apparantly - 'formats' => { 'default' => [ - 'country', - 'state', - 'county', - 'city', - '', #tax class - 'taxname', - 'tax', - 'old_tax', #old tax - ] }, - 'format_headers' => { 'default' => 1, }, - 'format_types' => { 'default' => 'xls' }, - }; - - #false laziness w/ - #FS::Record::process_batch_import( $job, $opt, @_ ); - - my $table = $opt->{table}; - my @pass_params = @{ $opt->{params} }; - my %formats = %{ $opt->{formats} }; - - my $param = shift; - warn Dumper($param) if $DEBUG; - - my $files = $param->{'uploaded_files'} - or die "No files provided.\n"; - - my (%files) = map { /^(\w+):([\.\w]+)$/ ? ($1,$2):() } split /,/, $files; - - my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc. '/'; - my $file = $dir. $files{'file'}; - - my $error = - #false laziness w/ - #FS::Record::batch_import( { - FS::cust_main_county::edit_import( { - #class-static - table => $table, - formats => \%formats, - format_types => $opt->{format_types}, - format_headers => $opt->{format_headers}, - format_sep_chars => $opt->{format_sep_chars}, - format_fixedlength_formats => $opt->{format_fixedlength_formats}, - #per-import - job => $job, - file => $file, - #type => $type, - format => $param->{format}, - params => { map { $_ => $param->{$_} } @pass_params }, - #? - default_csv => $opt->{default_csv}, - } ); - - unlink $file; - - die "$error\n" if $error; - -} - -=item edit_import - -=cut - -#false laziness w/ #FS::Record::batch_import, grep "edit_import" for differences -#could be turned into callbacks or something -use Text::CSV_XS; -sub edit_import { - my $param = shift; - - warn "$me edit_import call with params: \n". Dumper($param) - if $DEBUG; - - my $table = $param->{table}; - my $formats = $param->{formats}; - - my $job = $param->{job}; - my $file = $param->{file}; - my $format = $param->{'format'}; - my $params = $param->{params} || {}; - - die "unknown format $format" unless exists $formats->{ $format }; - - my $type = $param->{'format_types'} - ? $param->{'format_types'}{ $format } - : $param->{type} || 'csv'; - - unless ( $type ) { - if ( $file =~ /\.(\w+)$/i ) { - $type = lc($1); - } else { - #or error out??? - warn "can't parse file type from filename $file; defaulting to CSV"; - $type = 'csv'; - } - $type = 'csv' - if $param->{'default_csv'} && $type ne 'xls'; - } - - my $header = $param->{'format_headers'} - ? $param->{'format_headers'}{ $param->{'format'} } - : 0; - - my $sep_char = $param->{'format_sep_chars'} - ? $param->{'format_sep_chars'}{ $param->{'format'} } - : ','; - - my $fixedlength_format = - $param->{'format_fixedlength_formats'} - ? $param->{'format_fixedlength_formats'}{ $param->{'format'} } - : ''; - - my @fields = @{ $formats->{ $format } }; - - my $row = 0; - my $count; - my $parser; - my @buffer = (); - my @header = (); #edit_import - if ( $type eq 'csv' || $type eq 'fixedlength' ) { - - if ( $type eq 'csv' ) { - - my %attr = (); - $attr{sep_char} = $sep_char if $sep_char; - $parser = new Text::CSV_XS \%attr; - - } elsif ( $type eq 'fixedlength' ) { - - eval "use Parse::FixedLength;"; - die $@ if $@; - $parser = new Parse::FixedLength $fixedlength_format; - - } else { - die "Unknown file type $type\n"; - } - - @buffer = split(/\r?\n/, slurp($file) ); - splice(@buffer, 0, ($header || 0) ); - $count = scalar(@buffer); - - } elsif ( $type eq 'xls' ) { - - eval "use Spreadsheet::ParseExcel;"; - die $@ if $@; - - eval "use DateTime::Format::Excel;"; - #for now, just let the error be thrown if it is used, since only CDR - # formats bill_west and troop use it, not other excel-parsing things - #die $@ if $@; - - my $excel = Spreadsheet::ParseExcel::Workbook->new->Parse($file); - - $parser = $excel->{Worksheet}[0]; #first sheet - - $count = $parser->{MaxRow} || $parser->{MinRow}; - $count++; - - $row = $header || 0; - - #edit_import - need some magic to parse the header - if ( $header ) { - my @header_row = @{ $parser->{Cells}[$0] }; - @header = map $_->{Val}, @header_row; - } - - } else { - die "Unknown file type $type\n"; + # For internal use: + # + # When given two cust_main_county row objects, rewrite all database foreign + # key references referring to $row_to_merge->taxnum as references to + # $row_to_keep->taxnum, so $row_to_merge can be safely deleted from + # cust_main_county + # + # Usage (class method): + # $row_to_merge->_merge_into( $row_to_keep ) + # + # Usage (package function): + # FS::cust_main_county::_merge_into( $row_to_merge, $row_to_keep ) + # + # Optionally, allow merge when records don't match + # (useful during tax table update routines) + # $row_to_merge->_merge_info( + # $row_to_keep, + # { identical_record_check => 0 } + # ); + + my $row_to_merge = shift; + my $row_to_keep = shift + or croak 'record to merge into must be provided'; + + my $args = shift || { identical_record_check => 1 }; + croak 'invalid arguments hashref' unless ref $args; + + my $log = FS::Log->new('FS::cust_main_county'); + + my $keep_taxnum = $row_to_keep->taxnum; + my $merge_taxnum = $row_to_merge->taxnum; + + if ( + $args->{identical_record_check} + && ( + $row_to_keep->tax != $row_to_merge->tax + || $row_to_keep->exempt_amount != $row_to_merge->exempt_amount + ) + ) { + my $msg = "Found duplicate taxes (#$keep_taxnum and #$merge_taxnum) " + . "but they have different rates and can't be merged."; + $log->warn( $msg ); + warn "$msg\n"; + return; } - #my $columns; - - local $SIG{HUP} = 'IGNORE'; - local $SIG{INT} = 'IGNORE'; - local $SIG{QUIT} = 'IGNORE'; - local $SIG{TERM} = 'IGNORE'; - local $SIG{TSTP} = 'IGNORE'; - local $SIG{PIPE} = 'IGNORE'; - - my $oldAutoCommit = $FS::UID::AutoCommit; - local $FS::UID::AutoCommit = 0; - my $dbh = dbh; - - my $line; - my $imported = 0; - my( $last, $min_sec ) = ( time, 5 ); #progressbar foo - while (1) { - - my @columns = (); - if ( $type eq 'csv' ) { - - last unless scalar(@buffer); - $line = shift(@buffer); - - $parser->parse($line) or do { - $dbh->rollback if $oldAutoCommit; - return "can't parse: ". $parser->error_input(); - }; - @columns = $parser->fields(); - - } elsif ( $type eq 'fixedlength' ) { - - @columns = $parser->parse($line); - - } elsif ( $type eq 'xls' ) { - - last if $row > ($parser->{MaxRow} || $parser->{MinRow}) - || ! $parser->{Cells}[$row]; - - my @row = @{ $parser->{Cells}[$row] }; - @columns = map $_->{Val}, @row; - - #my $z = 'A'; - #warn $z++. ": $_\n" for @columns; - - } else { - die "Unknown file type $type\n"; - } - - #edit_import loop - - my %hash = %$params; - my @later; - - foreach my $field ( @fields ) { - - my $value = shift @columns; - - if ( ref($field) eq 'CODE' ) { - #&{$field}(\%hash, $value); - push @later, $field, $value; - } elsif ($field) { #edit_import - $hash{$field} = $value if defined($value) && length($value); + my $msg = "Merging tax #$merge_taxnum into #$keep_taxnum"; + $log->warn( $msg ); + warn "$msg\n"; + + foreach my $table (qw( + cust_bill_pkg_tax_location + cust_bill_pkg_tax_location_void + cust_tax_exempt_pkg + cust_tax_exempt_pkg_void + )) { + foreach my $row (qsearch($table, { 'taxnum' => $merge_taxnum })) { + $row->set('taxnum' => $keep_taxnum); + if ( my $error = $row->replace ) { + $log->error( $error ); + die $error; } - } - - my $class = "FS::$table"; - - my $record = $class->new( \%hash ); - - while ( scalar(@later) ) { - my $sub = shift @later; - my $data = shift @later; - &{$sub}($record, $data); #edit_import - don't have $conf - } - - #edit_import update or insert, not just insert - my $old = qsearchs({ - 'table' => $table, - 'hashref' => { map { $_ => $record->$_() } qw(country state county city taxname) }, - }); - - my $error; - if ( $old ) { - $record->taxnum($old->taxnum); - $error = $record->replace($old) - } else { - $record->insert; - } - - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "can't insert record". ( $line ? " for $line" : '' ). ": $error"; - } - - $row++; - $imported++; - - if ( $job && time - $min_sec > $last ) { #progress bar - $job->update_statustext( int(100 * $imported / $count) ); - $last = time; - } - } - $dbh->commit or die $dbh->errstr if $oldAutoCommit;; - - return "Empty file!" unless $imported || $param->{empty_ok}; - - ''; #no error - + if ( my $error = $row_to_merge->delete ) { + $log->error( $error ); + die $error; + } } sub _upgrade_data {