UI for per-city taxes (setup and assigning to customers/package locations), RT#5852
[freeside.git] / httemplate / browse / cust_main_county.cgi
1 <% include( 'elements/browse.html',
2      'title'          => "Tax Rates $title",
3      'name_singular'  => 'tax rate',
4      'menubar'        => \@menubar,
5      'html_init'      => $html_init,
6      'html_posttotal' => $html_posttotal,
7      'html_form'      => '<FORM NAME="taxesForm">',
8      'html_foot'      => $html_foot,
9      'query'          => {
10                            'table'    => 'cust_main_county',
11                            'hashref'  => $hashref,
12                            'order_by' =>
13                              'ORDER BY country, state, county, taxclass',
14                          },
15      'count_query'    => $count_query,
16      'header'         => \@header,
17      'header2'        => \@header2,
18      'fields'         => \@fields,
19      'align'          => $align,
20      'color'          => \@color,
21      'cell_style'     => \@cell_style,
22      'links'          => \@links,
23      'link_onclicks'  => \@link_onclicks,
24   )
25 %>
26 <%once>
27
28 my $conf = new FS::Conf;
29 my $money_char = $conf->config('money_char') || '$';
30
31 my $exempt_sub = sub {
32   my $cust_main_county = shift;
33
34   my @exempt = ();
35   push @exempt,
36        sprintf("$money_char%.2f&nbsp;per&nbsp;month", $cust_main_county->exempt_amount )
37     if $cust_main_county->exempt_amount > 0;
38
39   push @exempt, 'Setup&nbsp;fee'
40     if $cust_main_county->setuptax =~ /^Y$/i;
41
42   push @exempt, 'Recurring&nbsp;fee'
43     if $cust_main_county->recurtax =~ /^Y$/i;
44
45   [ map [ {'data'=>$_} ], @exempt ];
46 };
47
48 my $cs_oldrow;
49 my $cell_style;
50 my $cell_style_sub = sub {
51   my $row = shift;
52   if ( $cs_oldrow ne $row ) {
53     if ( $cs_oldrow ) {
54       if ( $cs_oldrow->country ne $row->country ) {
55         $cell_style = 'border-top:1px solid #000000';
56       } elsif ( $cs_oldrow->state ne $row->state ) {
57         $cell_style = 'border-top:1px solid #cccccc'; #default?
58       } elsif ( $cs_oldrow->state eq $row->state ) {
59         #$cell_style = 'border-top:dashed 1px dark gray';
60         $cell_style = 'border-top:1px dashed #cccccc';
61       }
62     }
63     $cs_oldrow = $row;
64   }
65   return $cell_style;
66 };
67
68 #my $edit_link = [ "${p}edit/cust_main_county.html", 'taxnum' ];
69 my $edit_link = [ 'javascript:void(0);', sub { ''; } ];
70
71 my $edit_onclick = sub {
72   my $row = shift;
73   my $taxnum = $row->taxnum;
74   include( '/elements/popup_link_onclick.html',
75              'action'      => "${p}edit/cust_main_county.html?$taxnum",
76              'actionlabel' => 'Edit tax rate',
77              'height'      => 420,
78              #default# 'width'  => 540,
79              #default# 'color' => '#333399',
80          );
81 };
82
83 my $ex_oldrow;
84 sub expand_link {
85   my %param = @_;
86
87   if ( $ex_oldrow eq $param{'row'} ) {
88     return '';
89   } else {
90     $ex_oldrow = $param{'row'};
91   }
92
93   my $taxnum = $param{'row'}->taxnum;
94   my $url = "${p}edit/cust_main_county-expand.cgi?$taxnum";
95
96   '<FONT SIZE="-1">'.
97     include( '/elements/popup_link.html',
98                'label'       => $param{'label'},
99                'action'      => $url,
100                'actionlabel' => $param{'desc'},
101                'height'      => 420,
102                #default# 'width'  => 540,
103                #default# 'color' => '#333399',
104            ).
105   '</FONT>';
106 }
107
108 sub collapse_link {
109   my %param = @_;
110
111   my $row = $param{'row'};
112   my $col = $param{'col'};
113   return ''
114     if $col eq 'county' and $row->city
115                             || qsearch({
116                                  'table'   => 'cust_main_county',
117                                  'hashref' => {
118                                    'country' => $row->country,
119                                    'state'   => $row->state,
120                                    'city'    => { op=>'!=', value=>'' },
121                                  },
122                                  'order_by' => 'LIMIT 1',
123                                });
124
125   my %above = ( 'city'   => 'county',
126                 'county' => 'state',
127               );
128
129   #XXX can still show the link when you have some counties broken down into
130   #cities and others not :/
131
132   my $taxnum = $param{'row'}->taxnum;
133   my $url = "${p}edit/process/cust_main_county-collapse.cgi?$taxnum";
134   $url = "javascript:collapse_areyousure('$url', '$col', '$above{$col}')";
135
136   qq(<FONT SIZE="-1"><A HREF="$url">$param{'label'}</A></FONT>);
137 }
138
139
140 sub separate_taxclasses_link {
141   my( $row ) = @_;
142   my $taxnum = $row->taxnum;
143   my $url = "${p}edit/process/cust_main_county-expand.cgi?taxclass=1;taxnum=$taxnum";
144
145   qq!<FONT SIZE="-1"><A HREF="$url">!;
146 }
147
148 #un-separate taxclasses too
149
150 </%once>
151 <%init>
152
153 die "access denied"
154   unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
155
156 #my $conf = new FS::Conf;
157 #my $money_char = $conf->config('money_char') || '$';
158 my $enable_taxclasses = $conf->exists('enable_taxclasses');
159
160 my @menubar;
161
162 my $html_init = <<END;
163   <SCRIPT>
164     function collapse_areyousure(href,col,above) {
165      if (confirm('Are you sure you want to remove all ' + col + ' tax rates for this ' + above + '?') == true)
166        window.location.href = href;
167     }
168   </SCRIPT>
169
170   Click on <u>add states</u> to specify a country's tax rates by state or province.
171   <BR>Click on <u>add counties</u> to specify a state's tax rates by county, or <u>remove counties</u> to remove per-county tax rates.
172   <BR>Click on <u>add cities</u> to specify a county's tax rates by city, or <u>remove cities</u> to remove per-city tax rates.
173 END
174
175 $html_init .= "<BR>Click on <u>separate taxclasses</u> to specify taxes per taxclass."
176   if $enable_taxclasses;
177 $html_init .= '<BR><BR>';
178
179 $html_init .= include('/elements/init_overlib.html');
180
181 my $title = '';
182
183 my $country = '';
184 if ( $cgi->param('country') =~ /^(\w\w)$/ ) {
185   $country = $1;
186   $title = $country;
187 }
188 $cgi->delete('country');
189
190 my $state = '';
191 if ( $country && $cgi->param('state') =~ /^([\w \-\'\[\]]+)$/ ) {
192   $state = $1;
193   $title = "$state, $title";
194 }
195 $cgi->delete('state');
196
197 my $county = '';
198 if ( $country && $state &&
199      $cgi->param('county') =~
200        /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]+)$/
201    )
202 {
203   $county = $1;
204   if ( $county eq '__NONE__' ) {
205     $title = "No county, $title";
206   } else {
207     $title = "$county county, $title";
208   }
209 }
210 $cgi->delete('county');
211
212 $title = " for $title" if $title;
213
214 my $taxclass = '';
215 if ( $cgi->param('taxclass') =~ /^([\w \-]+)$/ ) {
216   $taxclass = $1;
217   $title .= " for $taxclass tax class";
218 }
219 $cgi->delete('taxclass');
220
221 if ( $country || $taxclass ) {
222   push @menubar, 'View all tax rates' => $p.'browse/cust_main_county.cgi';
223 }
224
225 $cgi->param('dummy', 1);
226
227 my $filter_change =
228   "window.location = '". $cgi->self_url.
229   ";country=' + encodeURIComponent( document.getElementById('country').options[document.getElementById('country').selectedIndex].value ) + ".
230   "';state='   + encodeURIComponent( document.getElementById('state').options[document.getElementById('state').selectedIndex].value ) +".
231   "';county='  + encodeURIComponent( document.getElementById('county').options[document.getElementById('county').selectedIndex].value );";
232
233 #restore this so pagination works
234 $cgi->param('country',  $country) if $country;
235 $cgi->param('state',    $state  ) if $state;
236 $cgi->param('county',   $county ) if $county;
237 $cgi->param('taxclass', $county ) if $taxclass;
238
239 my $html_posttotal =
240   '<BR>( show country: '.
241   include('/elements/select-country.html',
242             'country'             => $country,
243             'onchange'            => $filter_change,
244             'empty_label'         => '(all)',
245             'disable_empty'       => 0,
246             'disable_stateupdate' => 1,
247          );
248
249 my %states_hash = $country ? states_hash($country) : ();
250 if ( scalar(keys(%states_hash)) > 1 ) {
251   $html_posttotal .=
252     ' show state: '.
253     include('/elements/select-state.html',
254               'country'              => $country,
255               'state'                => $state,
256               'onchange'             => $filter_change,
257               'empty_label'          => '(all)',
258               'disable_empty'        => 0,
259               'disable_countyupdate' => 1,
260            );
261 } else {
262   $html_posttotal .=
263     '<SELECT NAME="state" ID="state" STYLE="display:none">'.
264     '  <OPTION VALUE="" SELECTED>'.
265     '</SELECT>';
266 }
267
268 my @counties = ( $country && $state ) ? counties($state, $country) : ();
269 if ( scalar(@counties) > 1 ) {
270   $html_posttotal .=
271     ' show county: '.
272     include('/elements/select-county.html',
273               'country'              => $country,
274               'state'                => $state,
275               'county'               => $county,
276               'onchange'             => $filter_change,
277               'empty_label'          => '(all)',
278               'empty_data_label'     => '(none)',
279               'empty_data_value'     => '__NONE__',
280               'disable_empty'        => 0,
281               'disable_countyupdate' => 1,
282            );
283 } else {
284   $html_posttotal .=
285     '<SELECT NAME="county" ID="county" STYLE="display:none">'.
286     '  <OPTION VALUE="" SELECTED>'.
287     '</SELECT>';
288 }
289
290 $html_posttotal .= ' )';
291
292 my $bulk_popup_link = 
293   include( '/elements/popup_link_onclick.html',
294              'action'      => "${p}edit/bulk-cust_main_county.html?MAGIC_taxnum_MAGIC",
295              'actionlabel' => 'Bulk add new tax',
296              'nofalse'     => 1,
297              'height'      => 420,
298              #default# 'width'  => 540,
299              #default# 'color' => '#333399',
300          );
301
302 my $html_foot = <<END;
303 <SCRIPT TYPE="text/javascript">
304
305   function setAll(setTo) {
306     theForm = document.taxesForm;
307     for (i=0,n=theForm.elements.length;i<n;i++) {
308       if (theForm.elements[i].name.indexOf("cust_main_county") != -1) {
309         theForm.elements[i].checked = setTo;
310       }
311     }
312   }
313
314   function toggleAll() {
315     theForm = document.taxesForm;
316     for (i=0,n=theForm.elements.length;i<n;i++) {
317       if (theForm.elements[i].name.indexOf("cust_main_county") != -1) {
318         if ( theForm.elements[i].checked == true ) {
319           theForm.elements[i].checked = false;
320         } else {
321           theForm.elements[i].checked = true;
322         }
323       }
324     }
325   }
326
327   function bulkPopup() {
328     var bulk_popup_link = "$bulk_popup_link";
329     var bulkstring = '';
330     theForm = document.taxesForm;
331     for (i=0,n=theForm.elements.length;i<n;i++) {
332       if (    theForm.elements[i].name.indexOf("cust_main_county") != -1
333            && theForm.elements[i].checked == true
334          ) {
335         var name = theForm.elements[i].name;
336         var taxnum = name.replace(/cust_main_county/, '');
337         if ( bulkstring != '' ) {
338           bulkstring = bulkstring + ',';
339         }
340         bulkstring = bulkstring + taxnum;
341        
342       }
343     }
344     if ( bulk_popup_link.length > 1920 ) { // IE 2083 URL limit
345       alert('Too many selections'); // should do some session thing...
346       return false;
347     }
348     bulk_popup_link = bulk_popup_link.replace(/MAGIC_taxnum_MAGIC/, bulkstring);
349     eval(bulk_popup_link);
350   }
351
352 </SCRIPT>
353
354 <BR>
355 <A HREF="javascript:setAll(true)">select all</A> |
356 <A HREF="javascript:setAll(false)">unselect all</A> |
357 <A HREF="javascript:toggleAll()">toggle all</A>
358 <BR><BR>
359 <A HREF="javascript:void(0);" onClick="bulkPopup();">Add new tax to selected</A>
360
361 END
362
363 my $hashref = {};
364 my $count_query = 'SELECT COUNT(*) FROM cust_main_county';
365 if ( $country ) {
366   $hashref->{'country'} = $country;
367   $count_query .= ' WHERE country = '. dbh->quote($country);
368 }
369 if ( $state ) {
370   $hashref->{'state'} = $state;
371   $count_query .= ' AND state   = '. dbh->quote($state);
372 }
373 if ( $county ) {
374   if ( $county eq '__NONE__' ) {
375     $hashref->{'county'} = '';
376     $count_query .= " AND ( county = '' OR county IS NULL ) ";
377   } else {
378     $hashref->{'county'} = $county;
379     $count_query .= ' AND county  = '. dbh->quote($county);
380   }
381 }
382 if ( $taxclass ) {
383   $hashref->{'taxclass'} = $taxclass;
384   $count_query .= ( $count_query =~ /WHERE/i ? ' AND ' : ' WHERE ' ).
385                   ' taxclass  = '. dbh->quote($taxclass);
386 }
387
388
389 $cell_style = '';
390
391 my @header        = ( 'Country', 'State/Province', 'County', 'City' );
392 my @header2       = ( '', '', '', '', );
393 my @links         = ( '', '', '', '', );
394 my @link_onclicks = ( '', '', '', '', );
395 my $align = 'llll';
396
397 my @fields = (
398   sub { my $country = shift->country;
399         code2country($country). " ($country)";
400       },
401   sub { state_label($_[0]->state, $_[0]->country).
402         ( $_[0]->state
403             ? ''
404             : '&nbsp'. expand_link( desc  => 'Add States',
405                                     row   => $_[0],
406                                     label => 'add&nbsp;states',
407                                   )
408         )
409       },
410   sub { $_[0]->county
411           ? $_[0]->county. '&nbsp'.
412               collapse_link( col  => 'county',
413                              label=> 'remove&nbsp;counties',
414                              row  => $_[0],
415                            )
416           : '(all)&nbsp'.
417               expand_link(   desc  => 'Add Counties',
418                              row   => $_[0],
419                              label => 'add&nbsp;counties',
420                          );
421       },
422   sub { $_[0]->city
423           ? $_[0]->city. '&nbsp'.
424               collapse_link( col  => 'city',
425                              label=> 'remove&nbsp;cities',
426                              row  => $_[0],
427                            )
428           : '(all)&nbsp'.
429               expand_link(   desc  => 'Add Cities',
430                              row   => $_[0],
431                              label => 'add&nbsp;cities',
432                          );
433       },
434 );
435
436 my @color = (
437   '000000',
438   sub { shift->state  ? '000000' : '999999' },
439   sub { shift->county ? '000000' : '999999' },
440   sub { shift->city   ? '000000' : '999999' },
441 );
442
443 if ( $conf->exists('enable_taxclasses') ) {
444   push @header, qq!Tax class (<A HREF="${p}edit/part_pkg_taxclass.html">add new</A>)!;
445   push @header2, '(per-package classification)';
446   push @fields, sub { $_[0]->taxclass || '(all)&nbsp'.
447                        separate_taxclasses_link($_[0], 'Separate Taxclasses').
448                        'separate&nbsp;taxclasses</A></FONT>'
449                     };
450   push @color, sub { shift->taxclass ? '000000' : '999999' };
451   push @links, '';
452   push @link_onclicks, '';
453   $align .= 'l';
454 }
455
456 push @header,
457               '', #checkbox column
458               'Tax name',
459               'Rate', #'Tax',
460               'Exemptions',
461               ;
462
463 push @header2,
464                '',
465                '(printed on invoices)',
466                '',
467                '',
468                ;
469
470 my $newregion = 1;
471 my $cb_oldrow = '';
472 my $cb_sub = sub {
473   my $cust_main_county = shift;
474
475   if ( $cb_oldrow ) {
476     if (    $cb_oldrow->city     ne $cust_main_county->city 
477          || $cb_oldrow->county   ne $cust_main_county->county  
478          || $cb_oldrow->state    ne $cust_main_county->state  
479          || $cb_oldrow->country  ne $cust_main_county->country 
480          || $cb_oldrow->taxclass ne $cust_main_county->taxclass )
481     {
482       $newregion = 1;
483     } else {
484       $newregion = 0;
485     }  
486     
487   } else {
488     $newregion = 1;
489   }
490   $cb_oldrow = $cust_main_county;
491
492   if ( $newregion ) {
493     my $taxnum = $cust_main_county->taxnum;
494     qq!<INPUT NAME="cust_main_county$taxnum" TYPE="checkbox" VALUE="1">!;
495   } else {
496     '';
497   }
498 };
499
500 push @fields, 
501   $cb_sub,
502   sub { shift->taxname || 'Tax' },
503   sub { shift->tax. '%&nbsp;<FONT SIZE="-1">(edit)</FONT>' },
504   $exempt_sub,
505 ;
506
507 push @color,
508   '000000',
509   sub { shift->taxname ? '000000' : '666666' },
510   sub { shift->tax     ? '000000' : '666666' },
511   '000000',
512 ;
513
514 $align .= 'clrl';
515
516 my @cell_style = map $cell_style_sub, (1..scalar(@header));
517
518 push @links,         '', '', $edit_link,    '';
519 push @link_onclicks, '', '', $edit_onclick, '';
520
521 </%init>