From 0b94e40c533288be69a4fe60da36a385d31eff7f Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 12 Oct 2009 01:45:12 +0000 Subject: [PATCH] UI for per-city taxes (setup and assigning to customers/package locations), RT#5852 --- FS/FS/Mason.pm | 4 +- FS/FS/Misc.pm | 25 +++- FS/FS/Schema.pm | 12 +- FS/FS/cust_main_county.pm | 10 +- httemplate/browse/cust_main_county.cgi | 80 +++++++++--- httemplate/edit/cust_main.cgi | 18 ++- httemplate/edit/cust_main_county-expand.cgi | 6 +- .../edit/process/cust_main_county-collapse.cgi | 19 ++- .../edit/process/cust_main_county-expand.cgi | 15 ++- httemplate/elements/city.html | 142 +++++++++++++++++++++ httemplate/elements/location.html | 14 +- httemplate/elements/select-county.html | 32 +++-- httemplate/elements/tr-select-cust_location.html | 5 +- httemplate/misc/cities.cgi | 7 + 14 files changed, 318 insertions(+), 71 deletions(-) create mode 100644 httemplate/elements/city.html create mode 100644 httemplate/misc/cities.cgi diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index 6e6072ef7..98cf2549a 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -118,7 +118,9 @@ if ( -e $addl_handler_use_file ) { use FS::UI::Web::small_custview qw(small_custview); use FS::UI::bytecount; use FS::Msgcat qw(gettext geterror); - use FS::Misc qw( send_email send_fax states_hash counties state_label ); + use FS::Misc qw( send_email send_fax + states_hash counties cities state_label + ); use FS::Misc::eps2png qw( eps2png ); use FS::Report::Table::Monthly; use FS::TicketSystem; diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm index 5231350fa..dca906cde 100644 --- a/FS/FS/Misc.pm +++ b/FS/FS/Misc.pm @@ -14,7 +14,7 @@ use File::Temp; @ISA = qw( Exporter ); @EXPORT_OK = qw( generate_email send_email send_fax - states_hash counties state_label + states_hash counties cities state_label card_types generate_ps generate_pdf do_print csv_from_fixed @@ -556,6 +556,7 @@ Returns a list of counties for this state and country. sub counties { my( $state, $country ) = @_; + map { $_ } #return num_counties($state, $country) unless wantarray; sort map { s/[\n\r]//g; $_; } map { $_->county } qsearch({ @@ -567,6 +568,28 @@ sub counties { }); } +=item cities COUNTY STATE COUNTRY + +Returns a list of cities for this county, state and country. + +=cut + +sub cities { + my( $county, $state, $country ) = @_; + + map { $_ } #return num_cities($county, $state, $country) unless wantarray; + sort map { s/[\n\r]//g; $_; } + map { $_->city } + qsearch({ + 'select' => 'DISTINCT city', + 'table' => 'cust_main_county', + 'hashref' => { 'county' => $county, + 'state' => $state, + 'country' => $country, + }, + }); +} + =item state_label STATE COUNTRY_OR_LOCALE_SUBCOUNRY_OBJECT =cut diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 6f53b2aa7..946cf7f6d 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -405,12 +405,13 @@ sub tables_hashref { 'printed', 'int', '', '', '', '', #specific use cases - 'closed', 'char', 'NULL', 1, '', '', + 'closed', 'char', 'NULL', 1, '', '', #not yet used much 'statementnum', 'int', 'NULL', '', '', '', #invoice aggregate statements + 'agent_invid', 'int', 'NULL', '', '', '', #(varchar?) importing legacy ], 'primary_key' => 'invnum', - 'unique' => [], - 'index' => [ ['custnum'], ['_date'], ['statementnum'], ], + 'unique' => [ [ 'custnum', 'agent_invid' ] ], #agentnum? huh + 'index' => [ ['custnum'], ['_date'], ['statementnum'], ['agent_invid'] ], }, 'cust_statement' => { @@ -866,8 +867,9 @@ sub tables_hashref { # a tax rate. 'columns' => [ 'taxnum', 'serial', '', '', '', '', - 'state', 'varchar', 'NULL', $char_d, '', '', + 'city', 'varchar', 'NULL', $char_d, '', '', 'county', 'varchar', 'NULL', $char_d, '', '', + 'state', 'varchar', 'NULL', $char_d, '', '', 'country', 'char', '', 2, '', '', 'taxclass', 'varchar', 'NULL', $char_d, '', '', 'exempt_amount', @money_type, '', '', @@ -879,7 +881,7 @@ sub tables_hashref { 'primary_key' => 'taxnum', 'unique' => [], # 'unique' => [ ['taxnum'], ['state', 'county'] ], - 'index' => [ [ 'county' ], [ 'state' ], [ 'country' ], + 'index' => [ [ 'city' ], [ 'county' ], [ 'state' ], [ 'country' ], [ 'taxclass' ], ], }, diff --git a/FS/FS/cust_main_county.pm b/FS/FS/cust_main_county.pm index bb60abb45..ab1ac1e33 100644 --- a/FS/FS/cust_main_county.pm +++ b/FS/FS/cust_main_county.pm @@ -2,7 +2,7 @@ package FS::cust_main_county; use strict; use vars qw( @ISA @EXPORT_OK $conf - @cust_main_county %cust_main_county $countyflag ); + @cust_main_county %cust_main_county $countyflag ); # $cityflag ); use Exporter; use FS::Record qw( qsearch dbh ); use FS::cust_bill_pkg; @@ -17,6 +17,7 @@ use FS::cust_tax_exempt_pkg; @cust_main_county = (); $countyflag = ''; +#$cityflag = ''; #ask FS::UID to run this stuff for us later $FS::UID::callback{'FS::cust_main_county'} = sub { @@ -55,10 +56,12 @@ currently supported: =item taxnum - primary key (assigned automatically for new tax rates) -=item state +=item city =item county +=item state + =item country =item tax - percentage @@ -116,8 +119,9 @@ sub check { $self->exempt_amount(0) unless $self->exempt_amount; $self->ut_numbern('taxnum') - || $self->ut_anything('state') + || $self->ut_textn('city') || $self->ut_textn('county') + || $self->ut_anything('state') || $self->ut_text('country') || $self->ut_float('tax') || $self->ut_textn('taxclass') # ... diff --git a/httemplate/browse/cust_main_county.cgi b/httemplate/browse/cust_main_county.cgi index 232e6883c..d94e892ef 100755 --- a/httemplate/browse/cust_main_county.cgi +++ b/httemplate/browse/cust_main_county.cgi @@ -45,22 +45,22 @@ my $exempt_sub = sub { [ map [ {'data'=>$_} ], @exempt ]; }; -my $oldrow; +my $cs_oldrow; my $cell_style; my $cell_style_sub = sub { my $row = shift; - if ( $oldrow ne $row ) { - if ( $oldrow ) { - if ( $oldrow->country ne $row->country ) { + if ( $cs_oldrow ne $row ) { + if ( $cs_oldrow ) { + if ( $cs_oldrow->country ne $row->country ) { $cell_style = 'border-top:1px solid #000000'; - } elsif ( $oldrow->state ne $row->state ) { + } elsif ( $cs_oldrow->state ne $row->state ) { $cell_style = 'border-top:1px solid #cccccc'; #default? - } elsif ( $oldrow->state eq $row->state ) { + } elsif ( $cs_oldrow->state eq $row->state ) { #$cell_style = 'border-top:dashed 1px dark gray'; $cell_style = 'border-top:1px dashed #cccccc'; } } - $oldrow = $row; + $cs_oldrow = $row; } return $cell_style; }; @@ -80,9 +80,16 @@ my $edit_onclick = sub { ); }; +my $ex_oldrow; sub expand_link { my %param = @_; + if ( $ex_oldrow eq $param{'row'} ) { + return ''; + } else { + $ex_oldrow = $param{'row'}; + } + my $taxnum = $param{'row'}->taxnum; my $url = "${p}edit/cust_main_county-expand.cgi?$taxnum"; @@ -101,9 +108,30 @@ sub expand_link { sub collapse_link { my %param = @_; + my $row = $param{'row'}; + my $col = $param{'col'}; + return '' + if $col eq 'county' and $row->city + || qsearch({ + 'table' => 'cust_main_county', + 'hashref' => { + 'country' => $row->country, + 'state' => $row->state, + 'city' => { op=>'!=', value=>'' }, + }, + 'order_by' => 'LIMIT 1', + }); + + my %above = ( 'city' => 'county', + 'county' => 'state', + ); + + #XXX can still show the link when you have some counties broken down into + #cities and others not :/ + my $taxnum = $param{'row'}->taxnum; my $url = "${p}edit/process/cust_main_county-collapse.cgi?$taxnum"; - $url = "javascript:collapse_areyousure('$url')"; + $url = "javascript:collapse_areyousure('$url', '$col', '$above{$col}')"; qq($param{'label'}); } @@ -133,14 +161,15 @@ my @menubar; my $html_init = < - function collapse_areyousure(href) { - if (confirm("Are you sure you want to remove all county tax rates for this state?") == true) + function collapse_areyousure(href,col,above) { + if (confirm('Are you sure you want to remove all ' + col + ' tax rates for this ' + above + '?') == true) window.location.href = href; } Click on add states to specify a country's tax rates by state or province.
Click on add counties to specify a state's tax rates by county, or remove counties to remove per-county tax rates. +
Click on add cities to specify a county's tax rates by city, or remove cities to remove per-city tax rates. END $html_init .= "
Click on separate taxclasses to specify taxes per taxclass." @@ -359,11 +388,11 @@ if ( $taxclass ) { $cell_style = ''; -my @header = ( 'Country', 'State/Province', 'County',); -my @header2 = ( '', '', '', ); -my @links = ( '', '', '', ); -my @link_onclicks = ( '', '', '', ); -my $align = 'lll'; +my @header = ( 'Country', 'State/Province', 'County', 'City' ); +my @header2 = ( '', '', '', '', ); +my @links = ( '', '', '', '', ); +my @link_onclicks = ( '', '', '', '', ); +my $align = 'llll'; my @fields = ( sub { my $country = shift->country; @@ -380,7 +409,8 @@ my @fields = ( }, sub { $_[0]->county ? $_[0]->county. ' '. - collapse_link( label=> 'remove counties', + collapse_link( col => 'county', + label=> 'remove counties', row => $_[0], ) : '(all) '. @@ -389,12 +419,25 @@ my @fields = ( label => 'add counties', ); }, + sub { $_[0]->city + ? $_[0]->city. ' '. + collapse_link( col => 'city', + label=> 'remove cities', + row => $_[0], + ) + : '(all) '. + expand_link( desc => 'Add Cities', + row => $_[0], + label => 'add cities', + ); + }, ); my @color = ( '000000', sub { shift->state ? '000000' : '999999' }, sub { shift->county ? '000000' : '999999' }, + sub { shift->city ? '000000' : '999999' }, ); if ( $conf->exists('enable_taxclasses') ) { @@ -430,9 +473,10 @@ my $cb_sub = sub { my $cust_main_county = shift; if ( $cb_oldrow ) { - if ( $cb_oldrow->country ne $cust_main_county->country - || $cb_oldrow->state ne $cust_main_county->state + if ( $cb_oldrow->city ne $cust_main_county->city || $cb_oldrow->county ne $cust_main_county->county + || $cb_oldrow->state ne $cust_main_county->state + || $cb_oldrow->country ne $cust_main_county->country || $cb_oldrow->taxclass ne $cust_main_county->taxclass ) { $newregion = 1; diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi index 15c9f45b2..fac7ef27c 100755 --- a/httemplate/edit/cust_main.cgi +++ b/httemplate/edit/cust_main.cgi @@ -45,9 +45,11 @@ % my $same_checked = ''; % my $ship_disabled = ''; +% my @ship_style = (); % unless ( $cust_main->ship_last && $same ne 'Y' ) { % $same_checked = 'CHECKED'; -% $ship_disabled = 'DISABLED STYLE="background-color: #dddddd"'; +% $ship_disabled = 'DISABLED'; +% push @ship_style, 'background-color:#dddddd'; % foreach ( % qw( last first company address1 address2 city county state zip country % daytime night fax ) @@ -74,14 +76,20 @@ function bill_changed(what) { if ( what.form.same.checked ) { % for (qw( last first company address1 address2 city zip daytime night fax )) { - what.form.ship_<%$_%>.value = what.form.<%$_%>.value; % } what.form.ship_country.selectedIndex = what.form.country.selectedIndex; + function fix_ship_city() { + what.form.ship_city_select.selectedIndex = what.form.city_select.selectedIndex; + what.form.ship_city.style.display = what.form.city.style.display; + what.form.ship_city_select.style.display = what.form.city_select.style.display; + } + function fix_ship_county() { what.form.ship_county.selectedIndex = what.form.county.selectedIndex; + ship_county_changed(what.form.ship_county, fix_ship_city ); } function fix_ship_state() { @@ -97,7 +105,8 @@ function samechanged(what) { if ( what.checked ) { bill_changed(what); -% for (qw( last first company address1 address2 city county state zip country daytime night fax )) { +% my @fields = qw( last first company address1 address2 city city_select county state zip country daytime night fax ); +% for (@fields) { what.form.ship_<%$_%>.disabled = true; what.form.ship_<%$_%>.style.backgroundColor = '#dddddd'; % } @@ -111,7 +120,7 @@ function samechanged(what) { } else { -% for (qw( last first company address1 address2 city county state zip country daytime night fax )) { +% for (@fields) { what.form.ship_<%$_%>.disabled = false; what.form.ship_<%$_%>.style.backgroundColor = '#ffffff'; % } @@ -136,6 +145,7 @@ function samechanged(what) { 'pre' => 'ship_', 'onchange' => '', 'disabled' => $ship_disabled, + 'style' => \@ship_style ) %> diff --git a/httemplate/edit/cust_main_county-expand.cgi b/httemplate/edit/cust_main_county-expand.cgi index d5297ab0a..265dd1dab 100755 --- a/httemplate/edit/cust_main_county-expand.cgi +++ b/httemplate/edit/cust_main_county-expand.cgi @@ -37,9 +37,11 @@ my $cust_main_county = qsearchs('cust_main_county',{'taxnum'=>$taxnum}) my $title; -die "Can't expand entry!" if $cust_main_county->county; +die "Can't expand entry!" if $cust_main_county->city; -if ( $cust_main_county->state ) { +if ( $cust_main_county->county ) { + $title = 'Cities'; +} elsif ( $cust_main_county->state ) { $title = 'Counties'; } else { $title = 'States/Provinces'; diff --git a/httemplate/edit/process/cust_main_county-collapse.cgi b/httemplate/edit/process/cust_main_county-collapse.cgi index 18bd1fde2..9608fc919 100755 --- a/httemplate/edit/process/cust_main_county-collapse.cgi +++ b/httemplate/edit/process/cust_main_county-collapse.cgi @@ -12,10 +12,15 @@ my $cust_main_county = qsearchs('cust_main_county', { 'taxnum' => $taxnum } ) #really should do this in a .pm & start transaction -foreach my $delete ( qsearch('cust_main_county', { - 'country' => $cust_main_county->country, - 'state' => $cust_main_county->state - } ) ) { +my %search = ( + 'country' => $cust_main_county->country, + 'state' => $cust_main_county->state, + ); + +$search{'county'} = $cust_main_county->county + if $cust_main_county->city; + +foreach my $delete ( qsearch('cust_main_county', \%search) ) { # unless ( qsearch('cust_main',{ # 'state' => $cust_main_county->getfield('state'), # 'county' => $cust_main_county->getfield('county'), @@ -30,7 +35,11 @@ foreach my $delete ( qsearch('cust_main_county', { } $cust_main_county->taxnum(''); -$cust_main_county->county(''); +if ( $cust_main_county->city ) { + $cust_main_county->city(''); +} else { + $cust_main_county->county(''); +} my $error = $cust_main_county->insert; die $error if $error; diff --git a/httemplate/edit/process/cust_main_county-expand.cgi b/httemplate/edit/process/cust_main_county-expand.cgi index 04533a539..9984b08fa 100755 --- a/httemplate/edit/process/cust_main_county-expand.cgi +++ b/httemplate/edit/process/cust_main_county-expand.cgi @@ -48,17 +48,21 @@ foreach ( @expansion) { $new->setfield('taxclass', $_); } elsif ( ! $cust_main_county->state ) { $new->setfield('state',$_); - } else { + } elsif ( ! $cust_main_county->county ) { $new->setfield('county',$_); + } else { + #uppercase cities in the US to try and agree with USPS validation + $new->setfield('city', $new->country eq 'US' ? uc($_) : $_ ); } my $error = $new->insert; die $error if $error; } unless ( qsearch( 'cust_main', { - 'state' => $cust_main_county->state, - 'county' => $cust_main_county->county, - 'country' => $cust_main_county->country, + 'city' => $cust_main_county->city, + 'county' => $cust_main_county->county, + 'state' => $cust_main_county->state, + 'country' => $cust_main_county->country, } ) || ! @expansion ) { @@ -68,8 +72,9 @@ unless ( qsearch( 'cust_main', { if ( $cgi->param('taxclass') ) { print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi?". - 'state='. uri_escape($cust_main_county->state ).';'. + 'city='. uri_escape($cust_main_county->city ).';'. 'county='. uri_escape($cust_main_county->county ).';'. + 'state='. uri_escape($cust_main_county->state ).';'. 'country='. uri_escape($cust_main_county->country) ); myexit; diff --git a/httemplate/elements/city.html b/httemplate/elements/city.html new file mode 100644 index 000000000..1659ebf47 --- /dev/null +++ b/httemplate/elements/city.html @@ -0,0 +1,142 @@ +<%doc> + +Example: + + include( '/elements/city.html', + #recommended + country => $current_country, + state => $current_state, + county => $current_county, + city => $current_city, + + #optional + prefix => $optional_unique_prefix, + onchange => $javascript, + disabled => 0, #bool +# disable_empty => 1, #defaults to 1, disable the empty option +# empty_label => 'all', #label for empty option + style => [ 'attribute:value', 'another:value' ], + ); + + + +<% include('/elements/xmlhttp.html', + 'url' => $p.'misc/cities.cgi', + 'subs' => [ $pre. 'get_cities' ], + ) +%> + + + + + <% $text_style %> +> + + + +%# VALUE = "<% $curr_value |h %>" +<%init> + +my %opt = @_; + +my $pre = $opt{'prefix'}; + +my $text_style = $opt{'style'} ? [ @{ $opt{'style'} } ] : []; +my $select_style = $opt{'style'} ? [ @{ $opt{'style'} } ] : []; + +my @cities = cities( $opt{'county'}, $opt{'state'}, $opt{'country'} ); +if ( scalar(@cities) > 1 || $cities[0] ) { + push @$text_style, 'display:none'; +} else { + push @$select_style, 'display:none'; +} + +$text_style = + scalar(@$text_style) + ? 'STYLE="'. join(';', @$text_style). '"' + : ''; + +$select_style = + scalar(@$select_style) + ? 'STYLE="'. join(';', @$select_style). '"' + : ''; + + diff --git a/httemplate/elements/location.html b/httemplate/elements/location.html index 07aaa69f0..5478e1e1e 100644 --- a/httemplate/elements/location.html +++ b/httemplate/elements/location.html @@ -49,16 +49,7 @@ Example: <%$r%>City - - - <% $style %> - > - + <% include('/elements/city.html', %select_hash) %> ><%$r%>County <% include('/elements/select-county.html', %select_hash ) %> <%$r%>State @@ -123,7 +114,7 @@ $object->set($pre.'state', $statedefault ) || $object->get($pre.'country') ne $countrydefault; my @style = (); -push @style, 'background-color: #dddddd"' if $disabled; +push @style, 'background-color: #dddddd' if $disabled; my @address2_label_style = (); push @address2_label_style, 'visibility:hidden' @@ -152,6 +143,7 @@ my $county_style = : ''; my %select_hash = ( + 'city' => $object->get($pre.'city'), 'county' => $object->get($pre.'county'), 'state' => $object->get($pre.'state'), 'country' => $object->get($pre.'country'), diff --git a/httemplate/elements/select-county.html b/httemplate/elements/select-county.html index aa88abe96..aa9f453fd 100644 --- a/httemplate/elements/select-county.html +++ b/httemplate/elements/select-county.html @@ -67,8 +67,11 @@ Example: } //run the callback - if ( callback != null ) + if ( callback != null ) { callback(); + } else { + <% $pre %>county_changed(what.form.<% $pre %>county); + } } // go get the new counties @@ -80,7 +83,7 @@ Example: