fix unapplied payment report, RT#73048, fallout from #25944
[freeside.git] / httemplate / search / report_tax-xls.cgi
index bb843a7..30b32e8 100755 (executable)
@@ -1,15 +1,65 @@
 <%init>
-my $htmldoc = include('report_tax.cgi');
 
-my ($title) = ($htmldoc =~ /<title>\s*(.*)\s*<\/title>/i);
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
 
-# do this first so we can override the format if it's too many rows
-# attribs option: how to locate the table?  It's the only one with class="grid".
-my $te = HTML::TableExtract->new(attribs => {class => 'grid'});
-$te->parse($htmldoc);
-my $table = $te->first_table_found;
+my $DEBUG = $cgi->param('debug') || 0;
 
-my $override = ($table->row_count >= 65536 ? 'XLSX' : '');
+my $conf = new FS::Conf;
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+
+my %params = (
+  beginning => $beginning,
+  ending    => $ending,
+);
+$params{debug}   = $DEBUG;
+
+my $agentname;
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+  my $agent = FS::agent->by_key($1) or die "unknown agentnum $1";
+  $params{agentnum} = $1;
+  $agentname = $agent->agentname;
+}
+
+# credit date behavior: limit by the date of the credit application, or
+# the invoice?
+if ( $cgi->param('credit_date') eq 'cust_credit_bill' ) {
+  $params{credit_date} = 'cust_credit_bill';
+} else {
+  $params{credit_date} = 'cust_bill';
+}
+
+my $all = $cgi->param('all');
+my $report_class;
+
+if ( $all ) {
+  $report_class = 'FS::Report::Tax::All';
+} else {
+  $report_class = 'FS::Report::Tax::ByName';
+  $params{country} = $cgi->param('country');
+  $params{breakdown} = { map { $_ => 1 } $cgi->param('breakdown') };
+
+  # allow anything in here; FS::Report::Tax will treat it as unsafe
+  if ( length($cgi->param('taxname')) ) {
+    $params{taxname} = $cgi->param('taxname');
+  } else {
+    die "taxname required";
+  }
+}
+
+if ($DEBUG) {
+  warn "REPORT: $report_class\nPARAMS:\n".Dumper(\%params)."\n\n";
+}
+
+# generate the report
+my $report = $report_class->report(%params);
+my @rows = $report->table; # array of hashrefs
+
+my %pkgclass_name = map { $_->classnum, $_->classname } qsearch('pkg_class');
+$pkgclass_name{''} = 'Unclassified';
+
+my $override = (scalar(@rows) >= 65536 ? 'XLSX' : '');
 my $format = $FS::CurrentUser::CurrentUser->spreadsheet_format($override);
 my $filename = 'report_tax'.$format->{extension};
 
@@ -23,12 +73,17 @@ my $workbook = $format->{class}->new($XLS)
 
 # hardcoded formats, this could be handled better
 my $light_gray = $workbook->set_custom_color(63, '#eeeeee');
-my %format = (
+my %formatdef = (
   title => {
     size      => 24,
     align     => 'center',
     bg_color  => 'silver',
   },
+  sectionhead => {
+    size      => 11,
+    bold      => 1,
+    bg_color  => 'silver',
+  },
   colhead => {
     size      => 11,
     bold      => 1,
@@ -36,122 +91,243 @@ my %format = (
     valign    => 'vcenter',
     text_wrap => 1,
   },
+  colhead_small => {
+    size      => 8,
+    bold      => 1,
+    align     => 'center',
+    valign    => 'vcenter',
+    text_wrap => 1,
+  },
   rowhead => {
     size      => 11,
     valign    => 'bottom',
     text_wrap => 1,
   },
-  amount  => {
+  currency => {
+    size      => 11,
+    align     => 'right',
+    valign    => 'bottom',
+    num_format=> 8, # ($#,##0.00_);[Red]($#,##0.00)
+  },
+  number  => {
     size      => 11,
     align     => 'right',
     valign    => 'bottom',
-    num_format=> 8,
+    num_format=> 10, # 0.00%
   },
-  'size-1' => {
-    size      => 7.5,
+  bigmath => {
+    size      => 12,
     align     => 'center',
     valign    => 'vcenter',
     bold      => 1,
-    text_wrap => 1,
   },
-  'size+1' => {
-    size      => 12,
-    align     => 'center',
+  rowhead_outside => {
+    size      => 11,
+    align     => 'left',
     valign    => 'vcenter',
+    bg_color  => 'gray',
     bold      => 1,
+    italic    => 1,
   },
-  text => {
+  currency_outside => {
     size      => 11,
-    text_wrap => 1,
+    align     => 'right',
+    valign    => 'vcenter',
+    bg_color  => 'gray',
+    italic    => 1,
+    num_format=> 8, # ($#,##0.00_);[Red]($#,##0.00)
   },
+
 );
 my %default = (
   font      => 'Calibri',
-  bg_color  => $light_gray,
   border    => 1,
 );
 my @widths = ( #ick
-  18, (10.5, 3) x 6, 10.5, 10.5, 3, 10.5, 3, 10.5, 3, 10.5
+  30, (13) x 6, 3, 7.5, 3, 11, 11, 3, 11, 3, 11
 );
-foreach (keys(%format)) {
-  my %f = (%default, %{$format{$_}});
-  $format{$_} = $workbook->add_format(%f);
-  $format{"m_$_"} = $workbook->add_format(%f); # for merged cells
-  $format{"t_$_"} = $workbook->add_format(%f, bg_color => 'yellow'); # totals
+
+my @format = ( {}, {}, {} ); # white row, gray row, yellow (totals) row
+foreach (keys(%formatdef)) {
+  my %f = (%default, %{$formatdef{$_}});
+  $format[0]->{$_} = $workbook->add_format(%f);
+  $format[1]->{$_} = $workbook->add_format(bg_color => $light_gray, %f);
+  $format[2]->{$_} = $workbook->add_format(bg_color => 'yellow',
+                                           italic   => 1,
+                                           %f);
 }
-my $ws = $workbook->add_worksheet('taxreport');
-
-my @sheet;
-$sheet[0][0] = {
-  text    => $title,
-  format  => 'title',
-  colspan => '18',
-};  
+my $ws = $workbook->add_worksheet('Sales and Tax');
+
+# main title
+$ws->merge_range(0, 0, 0, 14, $report->title, $format[0]->{title});
+$ws->set_row(0, 30);
 # excel position
 my $x = 0;
-my $y = 3;
-foreach my $row ($table->rows()) {
+my $y = 2;
+
+my $colhead = $format[0]->{colhead};
+# print header
+$ws->merge_range($y, 1, $y, 6, 'Sales', $colhead);
+$ws->merge_range($y, 7, $y+1, 9, 'Rate', $colhead);
+$ws->merge_range($y, 10, $y, 16, 'Tax', $colhead);
+
+$y++;
+$colhead = $format[0]->{colhead_small};
+$ws->write($y, 1, [ 'Total',
+                    'Exempt customer',
+                    'Exempt package',
+                    'Monthly exemption',
+                    'Credited',
+                    'Taxable' ], $colhead);
+$ws->write($y, 10, 'Estimated', $colhead);
+$ws->write($y, 11, 'Invoiced', $colhead);
+$ws->write($y, 13, 'Credited', $colhead);
+$ws->write($y, 15, 'Net due',  $colhead);
+$ws->write($y, 16, 'Collected',$colhead);
+$y++;
+
+# print data
+my $rownum = 1;
+my $prev_row = { pkgclass => 'DUMMY PKGCLASS' };
+
+foreach my $row (@rows) {
   $x = 0;
-  $sheet[$y] = [];
-  foreach my $cell (@$row) {
-    if ($cell and ref($cell) eq 'HTML::ElementTable::DataElement') {
-      my $f = 'text';
-      if ( $cell->as_HTML =~ /font/i ) {
-        my ($el) = $cell->content_list;
-        $f = 'size'.$el->attr('size') if $el->attr('size');
-      }
-      elsif ( $cell->as_text =~ /^\$/ ) {
-        $f = 'amount'
-      }
-      elsif ( $cell->tag eq 'th' ) {
-        $f = 'colhead';
-      }
-      elsif ( $x == 0 ) {
-        $f = 'rowhead';
-      }
-      $sheet[$y][$x] = {
-        text    => $cell->as_text,
-        format  => $f,
-        rowspan => $cell->attr('rowspan'),
-        colspan => $cell->attr('colspan'),
-      };
+  if ( $row->{pkgclass} ne $prev_row->{pkgclass} ) {
+    $rownum = 1;
+    if ( $params{breakdown}->{pkgclass} ) {
+      $ws->merge_range($y, 0, $y, 15,
+        $pkgclass_name{$row->{pkgclass}},
+        $format[0]->{sectionhead}
+      );
+      $y++;
     }
+  }
+  # pick a format set
+  my $f = $format[$rownum % 2];
+  if ( $row->{total} ) {
+    $f = $format[2];
+  }
+  $ws->write($y, $x, $row->{label}, $f->{rowhead});
+  $x++;
+  foreach (qw(sales exempt_cust exempt_pkg exempt_monthly sales_credited taxable)) {
+    $ws->write($y, $x, $row->{$_} || 0, $f->{currency});
     $x++;
-  } #for $cell
+  }
+  $ws->write_string($y, $x, " \N{U+00D7} ", $f->{bigmath}); # MULTIPLICATION SIGN
+  $x++;
+  my $rate = $row->{rate};
+  $rate = $rate / 100 if $rate =~ /^[\d\.]+$/;
+  $ws->write($y, $x, $rate, $f->{number});
+  $x++;
+  $ws->write_string($y, $x, " = ", $f->{bigmath});
+  $x++;
+  my $estimated = $row->{estimated} || 0;
+  $estimated = '' if $rate eq 'variable';
+  $ws->write($y, $x, $estimated, $f->{currency});
+  $x++;
+  $ws->write($y, $x, $row->{tax} || 0, $f->{currency});
+  $x++;
+  $ws->write_string($y, $x, " \N{U+2212} ", $f->{bigmath}); # MINUS SIGN
+  $x++;
+  $ws->write($y, $x, $row->{tax_credited} || 0, $f->{currency});
+  $x++;
+  $ws->write_string($y, $x, " = ", $f->{bigmath});
+  $x++;
+  $ws->write($y, $x, $row->{tax} - $row->{tax_credited}, $f->{currency});
+  $x++;
+  $ws->write($y, $x, $row->{tax_paid} || 0, $f->{currency});
+
+  $rownum++;
   $y++;
+  $prev_row = $row;
+}
+
+# at the end of everything
+if ( $report->{out_sales} > 0 ) {
+  my $f = $format[0];
+  $ws->set_row($y, 30); # height
+  $ws->write($y, 0, mt('Out of taxable region'), $f->{rowhead_outside});
+  $ws->write($y, 1, $report->{out_sales}, $f->{currency_outside});
+  $y++;
+}
+
+# ewwwww...
+for my $x (0..scalar(@widths)-1) {
+  $ws->set_column($x, $x, $widths[$x]);
 }
 
-$y = 0;
-foreach my $row (@sheet) {
+# do the same for the credit worksheet
+$ws = $workbook->add_worksheet('Credits');
+
+my $title = $report->title;
+$title =~ s/Tax Report/Credits/;
+# main title
+$ws->merge_range(0, 0, 0, 14, $title, $format[0]->{title});
+$ws->set_row(0, 30); # height
+# excel position
+$x = 0;
+$y = 2;
+
+$colhead = $format[0]->{colhead};
+# print header
+$ws->merge_range($y, 1, $y+1, 1, 'Total', $colhead);
+$ws->merge_range($y, 2, $y, 4, 'Applied to', $colhead);
+
+$y++;
+$colhead = $format[0]->{colhead_small};
+$ws->write($y, 2, [ 'Taxable sales',
+                    'Tax-exempt sales',
+                    'Taxes'
+                  ], $colhead);
+$y++;
+
+# print data
+$rownum = 1;
+$prev_row = { pkgclass => 'DUMMY PKGCLASS' };
+
+foreach my $row (@rows) {
   $x = 0;
-  my $t_row = 1 if($row->[0]->{'text'} eq 'Total');
-  foreach my $cell (@$row) {
-    if ($cell) {
-      my $f = $cell->{format};
-      if ($cell->{rowspan} > 1 or $cell->{colspan} > 1) {
-        my $range = xl_range_formula(
-          'Taxreport', 
-          $y,
-          $y - 1 + ($cell->{rowspan} || 1),
-          $x,
-          $x - 1 + ($cell->{colspan} || 1)
-        );
-        $ws->merge_range($range, $cell->{text}, $format{"m_$f"});
-      }
-      else {
-        $f = "t_$f" if $t_row;
-        $ws->write($y, $x, $cell->{text}, $format{$f});
-      }
-    } #if $cell
+  if ( $row->{pkgclass} ne $prev_row->{pkgclass} ) {
+    $rownum = 1;
+    if ( $params{breakdown}->{pkgclass} ) {
+      $ws->merge_range($y, 0, $y, 4,
+        $pkgclass_name{$row->{pkgclass}},
+        $format[0]->{sectionhead}
+      );
+      $y++;
+    }
+  }
+  # pick a format set
+  my $f = $format[$rownum % 2];
+  if ( $row->{total} ) {
+    $f = $format[2];
+  }
+  $ws->write($y, $x, $row->{label}, $f->{rowhead});
+  $x++;
+  foreach (qw(credits sales_credited exempt_credited tax_credited)) {
+    $ws->write($y, $x, $row->{$_} || 0, $f->{currency});
     $x++;
   }
+
+  $rownum++;
   $y++;
+  $prev_row = $row;
 }
 
-for my $x (0..scalar(@widths)-1) {
+if ( $report->{out_credit} > 0 ) {
+  my $f = $format[0];
+  $ws->set_row($y, 30); # height
+  $ws->write($y, 0, mt('Out of taxable region'), $f->{rowhead_outside});
+  $ws->write($y, 1, $report->{out_credit}, $f->{currency_outside});
+  $y++;
+}
+
+
+for my $x (0..4) {
   $ws->set_column($x, $x, $widths[$x]);
 }
 
+
 $workbook->close;
 
 http_header('Content-Length' => length($data));