}
+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;
+ }
+}
+
sub _upgrade_data {
my $class = shift;
# assume taxes in Washington with district numbers, and null name, or
# named 'sales tax', are looked up via the wa_sales method. mark them.
- my $journal = 'cust_main_county__source_wa_sales';
+ my $journal = 'cust_main_county__source_wa_sales_201611';
if (!FS::upgrade_journal->is_done($journal)) {
my @taxes = qsearch({
'table' => 'cust_main_county',
}
FS::upgrade_journal->set_done($journal);
}
+ my @key_fields = (qw(city county state country district taxname taxclass));
+
# trim whitespace and convert to uppercase in the 'city' field.
foreach my $record (qsearch({
table => 'cust_main_county',
# create an exact duplicate.
# so find the record this one would duplicate, and merge them.
$record->check; # trims whitespace
- my %match = map { $_ => $record->get($_) }
- qw(city county state country district taxname taxclass);
+ my %match = map { $_ => $record->get($_) } @key_fields;
my $other = qsearchs('cust_main_county', \%match);
if ($other) {
- 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;
- }
+ $record->_merge_into($other);
} else {
# else there is no record this one duplicates, so just fix it
my $error = $record->replace;
die $error if $error;
}
} # foreach $record
+
+ # separate wa_sales taxes by tax class as needed
+ my $district_taxname = $conf->config('tax_district_taxname');
+ $journal = 'cust_main_county__district_taxclass';
+ if (!FS::upgrade_journal->is_done($journal)
+ and $conf->exists('enable_taxclasses')) {
+ eval "use FS::part_pkg_taxclass";
+ my @taxes = qsearch({
+ 'table' => 'cust_main_county',
+ 'extra_sql' => " WHERE tax > 0 AND country = 'US' AND state = 'WA'".
+ " AND district IS NOT NULL AND source = 'wa_sales'".
+ " AND taxclass IS NULL"
+ });
+ my @classes = FS::part_pkg_taxclass->taxclass_names;
+ if ( @taxes ) {
+ warn "Separating WA sales taxes: ".scalar(@taxes)." records.\n";
+ foreach my $oldtax (@taxes) {
+ my $error;
+ my $taxnum = $oldtax->taxnum;
+ warn "Separating tax #$taxnum into classes\n";
+ foreach my $taxclass (@classes) {
+ # ensure that we end up with a single copy of the tax in this
+ # jurisdiction+class. there may already be one (or more) there.
+ # if so, they all represent the same tax; merge them together.
+ my %newtax_hash = (
+ 'country' => 'US',
+ 'state' => 'WA',
+ 'city' => $oldtax->city,
+ 'district' => $oldtax->district,
+ 'taxclass' => $taxclass,
+ 'source' => 'wa_sales',
+ );
+ my @taxes_in_class = qsearch('cust_main_county', {
+ %newtax_hash,
+ 'tax' => { op => '>', value => 0 },
+ 'setuptax' => '',
+ 'recurtax' => '',
+ });
+ my $newtax = shift @taxes_in_class;
+ if ($newtax) {
+ foreach (@taxes_in_class) {
+ # allow the merge, even if this somehow differs.
+ $_->set('tax', $newtax->tax);
+ $_->_merge_into($newtax);
+ }
+ }
+ $newtax ||= FS::cust_main_county->new(\%newtax_hash);
+ # copy properties from the pre-split tax
+ $newtax->set('tax', $oldtax->tax);
+ $newtax->set('setuptax', $oldtax->setuptax);
+ $newtax->set('recurtax', $oldtax->recurtax);
+ # and assign the defined tax name
+ $newtax->set('taxname', $district_taxname);
+ $error = ($newtax->taxnum ? $newtax->replace : $newtax->insert);
+ die "splitting taxnum ".$oldtax->taxnum.": $error\n" if $error;
+ } # foreach $taxclass
+ $oldtax->set('tax', 0);
+ $error = $oldtax->replace;
+ die "splitting taxnum ".$oldtax->taxnum.": $error\n" if $error;
+ }
+ }
+ FS::upgrade_journal->set_done($journal);
+ }
+
+ # also ensure they all have the chosen taxname now
+ if ($district_taxname) {
+ my @taxes = qsearch('cust_main_county', {
+ 'source' => 'wa_sales',
+ 'taxname' => { op => '!=', value => $district_taxname }
+ });
+ if (@taxes) {
+ warn "Renaming WA sales taxes: ".scalar(@taxes)." records.\n";
+ foreach my $tax (@taxes) {
+ $tax->set('taxname', $district_taxname);
+ my $error = $tax->replace;
+ die "renaming taxnum ".$tax->taxnum.": $error\n" if $error;
+ }
+ }
+ }
+
+ # remove duplicates (except disabled records)
+ my @duplicate_sets = qsearch({
+ table => 'cust_main_county',
+ select => FS::Record::group_concat_sql('taxnum', ',') . ' AS taxnums, ' .
+ join(',', @key_fields),
+ extra_sql => ' WHERE tax > 0
+ GROUP BY city, county, state, country, district, taxname, taxclass
+ HAVING COUNT(*) > 1'
+ });
+ warn "Found ".scalar(@duplicate_sets)." set(s) of duplicate tax definitions\n"
+ if @duplicate_sets;
+ foreach my $set (@duplicate_sets) {
+ my @taxnums = split(',', $set->get('taxnums'));
+ my $first = FS::cust_main_county->by_key(shift @taxnums);
+ foreach my $taxnum (@taxnums) {
+ my $record = FS::cust_main_county->by_key($taxnum);
+ $record->_merge_into($first);
+ }
+ }
+
+
'';
}