Will things ever be the same again?
[freeside.git] / httemplate / search / report_tax.cgi
1 %
2 %
3 %my $conf = new FS::Conf;
4 %my $money_char = $conf->config('money_char') || '$';
5 %
6 %my $user = getotaker;
7 %
8 %my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
9 %
10 %my $join_cust = "
11 %    JOIN cust_bill USING ( invnum ) 
12 %    LEFT JOIN cust_main USING ( custnum )
13 %";
14 %my $from_join_cust = "
15 %    FROM cust_bill_pkg
16 %    $join_cust
17 %"; 
18 %my $join_pkg = "
19 %    LEFT JOIN cust_pkg USING ( pkgnum )
20 %    LEFT JOIN part_pkg USING ( pkgpart )
21 %";
22 %
23 %my $where = "WHERE _date >= $beginning AND _date <= $ending ";
24 %my @base_param = qw( county county state state country );
25 %if ( $conf->exists('tax-ship_address') ) {
26 %
27 %  $where .= "
28 %      AND (    (     ( ship_last IS NULL     OR  ship_last  = '' )
29 %                 AND ( county       = ? OR ? = '' )
30 %                 AND ( state        = ? OR ? = '' )
31 %                 AND   country      = ?
32 %               )
33 %            OR (       ship_last IS NOT NULL AND ship_last != ''
34 %                 AND ( ship_county  = ? OR ? = '' )
35 %                 AND ( ship_state   = ? OR ? = '' )
36 %                 AND   ship_country = ?
37 %               )
38 %          )
39 %  ";
40 %  #    AND payby != 'COMP'
41 %
42 %  push @base_param, @base_param;
43 %
44 %} else {
45 %
46 %  $where .= "
47 %      AND ( county  = ? OR ? = '' )
48 %      AND ( state   = ? OR ? = '' )
49 %      AND   country = ?
50 %  ";
51 %  #    AND payby != 'COMP'
52 %
53 %}
54 %
55 %my $agentname = '';
56 %if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
57 %  my $agent = qsearchs('agent', { 'agentnum' => $1 } );
58 %  die "agent not found" unless $agent;
59 %  $agentname = $agent->agent;
60 %  $where .= ' AND agentnum = '. $agent->agentnum;
61 %}
62 %
63 %my $gotcust = "
64 %  WHERE 0 < ( SELECT COUNT(*) FROM cust_main
65 %";
66 %if ( $conf->exists('tax-ship_address') ) {
67 %
68 %  $gotcust .= "
69 %                WHERE
70 %
71 %                (    cust_main_county.country = cust_main.country
72 %                  OR cust_main_county.country = cust_main.ship_country
73 %                )
74 %
75 %                AND
76 %
77 %                ( 
78 %
79 %                  (     ( ship_last IS NULL     OR  ship_last = '' )
80 %                    AND (    cust_main_county.country = cust_main.country )
81 %                    AND (    cust_main_county.state = cust_main.state
82 %                          OR cust_main_county.state = ''
83 %                          OR cust_main_county.state IS NULL )
84 %                    AND (    cust_main_county.county = cust_main.county
85 %                          OR cust_main_county.county = ''
86 %                          OR cust_main_county.county IS NULL )
87 %                  )
88 %  
89 %                  OR
90 %  
91 %                  (       ship_last IS NOT NULL AND ship_last != ''
92 %                    AND (    cust_main_county.country = cust_main.ship_country )
93 %                    AND (    cust_main_county.state = cust_main.ship_state
94 %                          OR cust_main_county.state = ''
95 %                          OR cust_main_county.state IS NULL )
96 %                    AND (    cust_main_county.county = cust_main.ship_county
97 %                          OR cust_main_county.county = ''
98 %                          OR cust_main_county.county IS NULL )
99 %                  )
100 %
101 %                )
102 %
103 %                LIMIT 1
104 %            )
105 %  ";
106 %
107 %} else {
108 %
109 %  $gotcust .= "
110 %                WHERE ( cust_main.county  = cust_main_county.county
111 %                        OR cust_main_county.county = ''
112 %                        OR cust_main_county.county IS NULL )
113 %                  AND ( cust_main.state   = cust_main_county.state
114 %                        OR cust_main_county.state = ''
115 %                        OR cust_main_county.state IS NULL )
116 %                  AND ( cust_main.country = cust_main_county.country )
117 %                LIMIT 1
118 %            )
119 %  ";
120 %
121 %}
122 %
123 %my($total, $tot_taxable, $owed, $tax) = ( 0, 0, 0, 0, 0 );
124 %my( $exempt_cust, $exempt_pkg, $exempt_monthly ) = ( 0, 0 );
125 %my $out = 'Out of taxable region(s)';
126 %my %regions = ();
127 %foreach my $r (qsearch('cust_main_county', {}, '', $gotcust) ) {
128 %  #warn $r->county. ' '. $r->state. ' '. $r->country. "\n";
129 %
130 %  my $label = getlabel($r);
131 %  $regions{$label}->{'label'} = $label;
132 %  $regions{$label}->{'url_param'} = join(';', map "$_=".$r->$_(), qw( county state country ) );
133 %
134 %  my @param = @base_param;
135 %  my $mywhere = $where;
136 %
137 %  if ( $r->taxclass ) {
138 %    $mywhere .= " AND taxclass = ? ";
139 %    push @param, 'taxclass';
140 %    $regions{$label}->{'url_param'} .= ';taxclass='. $r->taxclass
141 %      if $cgi->param('show_taxclasses');
142 %  }
143 %
144 %  my $fromwhere = $from_join_cust. $join_pkg. $mywhere. " AND payby != 'COMP' ";
145 %
146 %#  my $label = getlabel($r);
147 %#  $regions{$label}->{'label'} = $label;
148 %
149 %  my $nottax = 'pkgnum != 0';
150 %
151 %  ## calculate total for this region
152 %
153 %  my $t = scalar_sql($r, \@param,
154 %    "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) $fromwhere AND $nottax"
155 %  );
156 %  $total += $t;
157 %  $regions{$label}->{'total'} += $t;
158 %
159 %  ## calculate customer-exemption for this region
160 %
161 %##  my $taxable = $t;
162 %
163 %#  my($taxable, $x_cust) = (0, 0);
164 %#  foreach my $e ( grep { $r->get($_.'tax') !~ /^Y/i }
165 %#                       qw( cust_bill_pkg.setup cust_bill_pkg.recur ) ) {
166 %#    $taxable += scalar_sql($r, \@param, 
167 %#      "SELECT SUM($e) $fromwhere AND $nottax AND ( tax != 'Y' OR tax IS NULL )"
168 %#    );
169 %#
170 %#    $x_cust += scalar_sql($r, \@param, 
171 %#      "SELECT SUM($e) $fromwhere AND $nottax AND tax = 'Y'"
172 %#    );
173 %#  }
174 %
175 %  my $x_cust = scalar_sql($r, \@param,
176 %    "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur)
177 %     $fromwhere AND $nottax AND tax = 'Y' "
178 %  );
179 %
180 %  $exempt_cust += $x_cust;
181 %  $regions{$label}->{'exempt_cust'} += $x_cust;
182 %  
183 %  ## calculate package-exemption for this region
184 %
185 %  my $x_pkg = scalar_sql($r, \@param,
186 %    "SELECT SUM(
187 %                 ( CASE WHEN part_pkg.setuptax = 'Y'
188 %                        THEN cust_bill_pkg.setup
189 %                        ELSE 0
190 %                   END
191 %                 )
192 %                 +
193 %                 ( CASE WHEN part_pkg.recurtax = 'Y'
194 %                        THEN cust_bill_pkg.recur
195 %                        ELSE 0
196 %                   END
197 %                 )
198 %               )
199 %       $fromwhere
200 %       AND $nottax
201 %       AND (
202 %                ( part_pkg.setuptax = 'Y' AND cust_bill_pkg.setup > 0 )
203 %             OR ( part_pkg.recurtax = 'Y' AND cust_bill_pkg.recur > 0 )
204 %           )
205 %       AND ( tax != 'Y' OR tax IS NULL )
206 %    "
207 %  );
208 %  $exempt_pkg += $x_pkg;
209 %  $regions{$label}->{'exempt_pkg'} += $x_pkg;
210 %
211 %  ## calculate monthly exemption (texas tax) for this region
212 %
213 %  # count up all the cust_tax_exempt_pkg records associated with
214 %  # the actual line items.
215 %
216 %  my $x_monthly = scalar_sql($r, \@param,
217 %    "SELECT SUM(amount)
218 %       FROM cust_tax_exempt_pkg
219 %       JOIN cust_bill_pkg USING ( billpkgnum )
220 %       $join_cust $join_pkg
221 %     $mywhere"
222 %  );
223 %#  if ( $x_monthly ) {
224 %#    #warn $r->taxnum(). ": $x_monthly\n";
225 %#    $taxable -= $x_monthly;
226 %#  }
227 %
228 %  $exempt_monthly += $x_monthly;
229 %  $regions{$label}->{'exempt_monthly'} += $x_monthly;
230 %
231 %  my $taxable = $t - $x_cust - $x_pkg - $x_monthly;
232 %
233 %  $tot_taxable += $taxable;
234 %  $regions{$label}->{'taxable'} += $taxable;
235 %
236 %  $owed += $taxable * ($r->tax/100);
237 %  $regions{$label}->{'owed'} += $taxable * ($r->tax/100);
238 %
239 %  if ( defined($regions{$label}->{'rate'})
240 %       && $regions{$label}->{'rate'} != $r->tax.'%' ) {
241 %    $regions{$label}->{'rate'} = 'variable';
242 %  } else {
243 %    $regions{$label}->{'rate'} = $r->tax.'%';
244 %  }
245 %
246 %}
247 %
248 %my $taxwhere = "$from_join_cust $where AND payby != 'COMP' ";
249 %my @taxparam = @base_param;
250 %my %base_regions = ();
251 %#foreach my $label ( keys %regions ) {
252 %foreach my $r (
253 %  qsearch( 'cust_main_county',
254 %           {},
255 %           'DISTINCT ON (country, state, county, taxname) *',
256 %           $gotcust
257 %         )
258 %) {
259 %
260 %  #warn join('-', map { $r->$_() } qw( country state county taxname ) )."\n";
261 %
262 %  my $label = getlabel($r);
263 %
264 %  #my $fromwhere = $join_pkg. $where. " AND payby != 'COMP' ";
265 %  #my @param = @base_param; 
266 %
267 %  #match itemdesc if necessary!
268 %  my $named_tax =
269 %    $r->taxname
270 %      ? 'AND itemdesc = '. dbh->quote($r->taxname)
271 %      : "AND ( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )";
272 %  my $x = scalar_sql($r, \@taxparam,
273 %    "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) $taxwhere ".
274 %    "AND pkgnum = 0 $named_tax",
275 %  );
276 %  $tax += $x;
277 %  $regions{$label}->{'tax'} += $x;
278 %
279 %  if ( $cgi->param('show_taxclasses') ) {
280 %    my $base_label = getlabel($r, 'no_taxclass'=>1 );
281 %    $base_regions{$base_label}->{'label'} = $base_label;
282 %    $base_regions{$base_label}->{'url_param'} =
283 %      join(';', map "$_=".$r->$_(), qw( county state country ) );
284 %    $base_regions{$base_label}->{'tax'} += $x;
285 %  }
286 %
287 %}
288 %
289 %#ordering
290 %my @regions =
291 %  map $regions{$_},
292 %  sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) }
293 %  keys %regions;
294 %
295 %my @base_regions =
296 %  map $base_regions{$_},
297 %  sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) }
298 %  keys %base_regions;
299 %
300 %push @regions, {
301 %  'label'          => 'Total',
302 %  'url_param'      => '',
303 %  'total'          => $total,
304 %  'exempt_cust'    => $exempt_cust,
305 %  'exempt_pkg'     => $exempt_pkg,
306 %  'exempt_monthly' => $exempt_monthly,
307 %  'taxable'        => $tot_taxable,
308 %  'rate'           => '',
309 %  'owed'           => $owed,
310 %  'tax'            => $tax,
311 %};
312 %
313 %#-- 
314 %
315 %sub getlabel {
316 %  my $r = shift;
317 %  my %opt = @_;
318 %
319 %  my $label;
320 %  if (
321 %    $r->tax == 0 
322 %    && ! scalar( qsearch('cust_main_county', { 'state'   => $r->state,
323 %                                               'county'  => $r->county,
324 %                                               'country' => $r->country,
325 %                                               'tax' => { op=>'>', value=>0 },
326 %                                             }
327 %                        )
328 %               )
329 %
330 %  ) {
331 %    #kludge to avoid "will not stay shared" warning
332 %    my $out = 'Out of taxable region(s)';
333 %    $label = $out;
334 %  } elsif ( $r->taxname ) {
335 %    $label = $r->taxname;
336 %#    $regions{$label}->{'taxname'} = $label;
337 %#    push @{$regions{$label}->{$_}}, $r->$_() foreach qw( county state country );
338 %  } else {
339 %    $label = $r->country;
340 %    $label = $r->state.", $label" if $r->state;
341 %    $label = $r->county." county, $label" if $r->county;
342 %    $label = "$label (". $r->taxclass. ")"
343 %      if $r->taxclass
344 %      && $cgi->param('show_taxclasses')
345 %      && ! $opt{'no_taxclass'};
346 %    #$label = $r->taxname. " ($label)" if $r->taxname;
347 %  }
348 %  return $label;
349 %}
350 %
351 %#false laziness w/FS::Report::Table::Monthly (sub should probably be moved up
352 %#to FS::Report or FS::Record or who the fuck knows where)
353 %sub scalar_sql {
354 %  my( $r, $param, $sql ) = @_;
355 %  #warn "$sql\n";
356 %  my $sth = dbh->prepare($sql) or die dbh->errstr;
357 %  $sth->execute( map $r->$_(), @$param )
358 %    or die "Unexpected error executing statement $sql: ". $sth->errstr;
359 %  $sth->fetchrow_arrayref->[0] || 0;
360 %}
361 %
362 %
363 %
364 %my $dateagentlink = "begin=$beginning;end=$ending";
365 %$dateagentlink .= ';agentnum='. $cgi->param('agentnum')
366 %  if length($agentname);
367 %my $baselink   = $p. "search/cust_bill_pkg.cgi?$dateagentlink";
368 %my $exemptlink = $p. "search/cust_tax_exempt_pkg.cgi?$dateagentlink";
369 %
370
371
372 <% include("/elements/header.html", "$agentname Sales Tax Report - ".
373               ( $beginning
374                   ? time2str('%h %o %Y ', $beginning )
375                   : ''
376               ).
377               'through '.
378               ( $ending == 4294967295
379                   ? 'now'
380                   : time2str('%h %o %Y', $ending )
381               ),
382             menubar( 'Main Menu'=>$p, )
383           )
384 %>
385
386 <% include('/elements/table-grid.html') %>
387
388   <TR>
389     <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH>
390     <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=9>Sales</TH>
391     <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH>
392     <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Rate</TH>
393     <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH>
394     <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax owed</TH>
395 % unless ( $cgi->param('show_taxclasses') ) { 
396
397       <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax invoiced</TH>
398 % } 
399
400   </TR>
401   <TR>
402     <TH CLASS="grid" BGCOLOR="#cccccc">Total</TH>
403     <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
404     <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(tax-exempt customer)</FONT></TH>
405     <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
406     <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(tax-exempt package)</FONT></TH>
407     <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
408     <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(monthly exemption)</FONT></TH>
409     <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
410     <TH CLASS="grid" BGCOLOR="#cccccc">Taxable</TH>
411   </TR>
412 % my $bgcolor1 = '#eeeeee';
413 %   my $bgcolor2 = '#ffffff';
414 %   my $bgcolor;
415 %
416 % foreach my $region ( @regions ) {
417 %
418 %       if ( $bgcolor eq $bgcolor1 ) {
419 %         $bgcolor = $bgcolor2;
420 %       } else {
421 %         $bgcolor = $bgcolor1;
422 %       }
423 %
424 %       my $link = '';
425 %       if ( $region->{'label'} ne 'Total' ) {
426 %         if ( $region->{'label'} eq $out ) {
427 %           $link = ';out=1';
428 %         } else {
429 %           $link = ';'. $region->{'url_param'};
430 %         }
431 %       }
432 %
433 %
434 %
435 %
436 %  
437
438
439     <TR>
440       <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} %></TD>
441       <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
442         <A HREF="<% $baselink. $link %>;nottax=1"><% $money_char %><% sprintf('%.2f', $region->{'total'} ) %></A>
443       </TD>
444       <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD>
445       <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
446         <A HREF="<% $baselink. $link %>;nottax=1;cust_tax=Y"><% $money_char %><% sprintf('%.2f', $region->{'exempt_cust'} ) %></A>
447       </TD>
448       <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD>
449       <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
450         <A HREF="<% $baselink. $link %>;nottax=1;pkg_tax=Y"><% $money_char %><% sprintf('%.2f', $region->{'exempt_pkg'} ) %></A>
451       </TD>
452       <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD>
453       <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
454         <A HREF="<% $exemptlink. $link %>"><% $money_char %><% sprintf('%.2f', $region->{'exempt_monthly'} ) %></A>
455         </TD>
456       <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> = </B></FONT></TD>
457       <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
458         <% $money_char %><% sprintf('%.2f', $region->{'taxable'} ) %></A>
459       </TD>
460       <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} eq 'Total' ? '' : '<FONT FACE="sans-serif" SIZE="+1"><B> X </B></FONT>' %></TD>
461       <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"><% $region->{'rate'} %></TD>
462       <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} eq 'Total' ? '' : '<FONT FACE="sans-serif" SIZE="+1"><B> = </B></FONT>' %></TD>
463       <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
464         <% $money_char %><% sprintf('%.2f', $region->{'owed'} ) %>
465       </TD>
466 % unless ( $cgi->param('show_taxclasses') ) { 
467
468         <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
469           <A HREF="<% $baselink. $link %>;istax=1"><% $money_char %><% sprintf('%.2f', $region->{'tax'} ) %></A>
470         </TD>
471 % } 
472
473     </TR>
474 % } 
475
476
477 </TABLE>
478 % if ( $cgi->param('show_taxclasses') ) { 
479
480
481   <BR>
482   <% include('/elements/table-grid.html') %>
483   <TR>
484     <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
485     <TH CLASS="grid" BGCOLOR="#cccccc">Tax invoiced</TH>
486   </TR>
487 % #some false laziness w/above
488 %     $bgcolor1 = '#eeeeee';
489 %     $bgcolor2 = '#ffffff';
490 %     foreach my $region ( @base_regions ) {
491 %
492 %       if ( $bgcolor eq $bgcolor1 ) {
493 %         $bgcolor = $bgcolor2;
494 %       } else {
495 %         $bgcolor = $bgcolor1;
496 %       }
497 %
498 %       my $link = '';
499 %       #if ( $region->{'label'} ne 'Total' ) {
500 %         if ( $region->{'label'} eq $out ) {
501 %           $link = ';out=1';
502 %         } else {
503 %           $link = ';'. $region->{'url_param'};
504 %         }
505 %       #}
506 %  
507
508
509     <TR>
510       <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} %></TD>
511       <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
512         <A HREF="<% $baselink. $link %>;istax=1"><% $money_char %><% sprintf('%.2f', $region->{'tax'} ) %></A>
513       </TD>
514     </TR>
515 % } 
516 %
517 %     if ( $bgcolor eq $bgcolor1 ) {
518 %       $bgcolor = $bgcolor2;
519 %     } else {
520 %       $bgcolor = $bgcolor1;
521 %     }
522 %  
523
524
525   <TR>
526    <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">Total</TD>
527     <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
528       <A HREF="<% $baselink %>;istax=1"><% $money_char %><% sprintf('%.2f', $tax ) %></A>
529     </TD>
530   </TR>
531
532   </TABLE>
533 % } 
534
535
536 </BODY>
537 </HTML>
538
539