#!/usr/bin/perl

=head1 fix-missing-taxes

Usage:
  fix-missing-taxes <user> <start date>

This script fixes CCH taxes that were calculated incorrectly due to a bug 
in bundled package behavior in March 2014.  For all invoices since the start
date, it recalculates taxes on all the non-tax items, generates credits for
taxes that were originally overcharged, and creates new invoices for taxes
that were undercharged.

=cut

use FS::UID qw(adminsuidsetup dbh);
use FS::cust_bill;
use FS::Record qw(qsearch);
use List::Util 'sum';
use DateTime::Format::Natural;

use strict;

my $usage = "usage: fix-missing-taxes <user> <start date>\n" ;
my $user = shift or die $usage;
adminsuidsetup($user);

$FS::UID::AutoCommit = 0;

my $parser = DateTime::Format::Natural->new;
my $dt = $parser->parse_datetime(shift);
die $usage unless $parser->success;

my $date_filter = { _date => { op => '>=', value => $dt->epoch } };
my @bills = qsearch('cust_bill', $date_filter);

warn "Examining ".scalar(@bills)." invoices...\n";

my %new_tax_items; # custnum => [ new taxes to charge ]
my %cust_credits; # custnum => { tax billpkgnum => credit amount }

foreach my $cust_bill (@bills) {
  my $cust_main = $cust_bill->cust_main;
  my $custnum = $cust_main->custnum;
  my %taxlisthash;
  my %old_tax;
  my @nontax_items;

  foreach my $item ($cust_bill->cust_bill_pkg) {
    if ( $item->pkgnum == 0 ) {
      $old_tax{ $item->itemdesc } = $item;
    } else {
      $cust_main->_handle_taxes( \%taxlisthash, $item );
      push @nontax_items, $item;
    }
  }
  my $tax_lines = $cust_main->calculate_taxes(
    \@nontax_items,
    \%taxlisthash,
    $cust_bill->_date
  );

  my %new_tax = map { $_->itemdesc, $_ } @$tax_lines;
  my %all = (%old_tax, %new_tax);
  foreach my $taxname (keys(%all)) {
    my $delta = sprintf('%.2f',
                  ($new_tax{$taxname} ? $new_tax{$taxname}->setup : 0) -
                  ($old_tax{$taxname} ? $old_tax{$taxname}->setup : 0)
                );
    if ( $delta >= 0.01 ) {
      # create a tax adjustment
      $new_tax_items{$custnum} ||= [];
      my $item = $new_tax{$taxname};
      foreach (@{ $item->cust_bill_pkg_tax_rate_location }) {
        $_->set('amount',
          sprintf('%.2f', $_->get('amount') * $delta / $item->get('setup'))
        );
      }
      $item->set('setup', $delta);
      push @{ $new_tax_items{$custnum} }, $new_tax{$taxname};
    } elsif ( $delta <= -0.01 ) {
      my $old_tax_item = $old_tax{$taxname};
      $cust_credits{$custnum} ||= {};
      $cust_credits{$custnum}{ $old_tax_item->billpkgnum } = -1 * $delta;
    }
  }
}

my $num_bills = 0;
my $amt_billed = 0;
# create new invoices for those that need them
foreach my $custnum (keys %new_tax_items) {
  my $cust_main = FS::cust_main->by_key($custnum);
  my @cust_bill = $cust_main->cust_bill;
  my $balance = $cust_main->balance;
  my $previous_bill = $cust_bill[-1] if @cust_bill;
  my $previous_balance = 0;
  if ( $previous_bill ) {
    $previous_balance = $previous_bill->billing_balance
                      + $previous_bill->charged;
  }

  my $lines = $new_tax_items{$custnum};
  my $total = sum( map { $_->setup } @$lines);
  my $new_bill = FS::cust_bill->new({
      'custnum'           => $custnum,
      '_date'             => $^T,
      'charged'           => sprintf('%.2f', $total),
      'billing_balance'   => $balance,
      'previous_balance'  => $previous_balance,
      'cust_bill_pkg'     => $lines,
  });
  my $error = $new_bill->insert;
  die "error billing cust#$custnum\n" if $error;
  $num_bills++;
  $amt_billed += $total;
}
print "Created $num_bills bills for a total of \$$amt_billed.\n";

my $credit_reason = FS::reason->new_or_existing( 
  reason  => 'Sales tax correction',
  class   => 'R',
  type    => 'Credit',
);

my $num_credits = 0;
my $amt_credited = 0;
# create credits for those that need them
foreach my $custnum (keys %cust_credits) {
  my $cust_main = FS::cust_main->by_key($custnum);
  my $lines = $cust_credits{$custnum};
  my @billpkgnums = keys %$lines;
  my @amounts = values %$lines;
  my $total = sprintf('%.2f', sum(@amounts));
  next if $total < 0.01;
  my $error = FS::cust_credit->credit_lineitems(
    'custnum'     => $custnum,
    'billpkgnums' => \@billpkgnums,
    'setuprecurs' => [ map {'setup'} @billpkgnums ],
    'amounts'     => \@amounts,,
    'apply'       => 1,
    'amount'      => $total,
    'reasonnum'   => $credit_reason->reasonnum,
  );
  die "error crediting cust#$custnum\n" if $error;
  $num_credits++;
  $amt_credited += $total;
}
print "Created $num_credits credits for a total of \$$amt_credited.\n";

dbh->commit;
