UI improvements for selecting taxproducts, #71555 and #71556
authorMark Wells <mark@freeside.biz>
Tue, 6 Sep 2016 19:42:34 +0000 (12:42 -0700)
committerMark Wells <mark@freeside.biz>
Tue, 6 Sep 2016 20:30:45 +0000 (13:30 -0700)
httemplate/browse/part_pkg.cgi
httemplate/browse/part_pkg_taxproduct/suretax.html
httemplate/elements/select-taxproduct.html
httemplate/elements/tr-part_pkg-taxproducts.html
httemplate/misc/taxproduct.cgi [new file with mode: 0644]

index 1e8b510..acc3211 100755 (executable)
                  'html_foot'             => $html_foot,
              )
 %>
+<%def .style>
+<STYLE TYPE="text/css">
+  .taxproduct_desc {
+    color: blue;
+    text-decoration: underline dotted;
+  }
+</STYLE>
+<SCRIPT TYPE="text/javascript">
+$().ready(function() {
+  $('.taxproduct_desc').tooltip({});
+});
+</SCRIPT>
+</%def>
 <%init>
 
 my $curuser = $FS::CurrentUser::CurrentUser;
@@ -45,6 +58,7 @@ die "access denied"
 
 my $conf = new FS::Conf;
 my $taxclasses = $conf->exists('enable_taxclasses');
+my $taxvendor = $conf->config('tax_data_vendor');
 my $money_char = $conf->config('money_char') || '$';
 
 my $select = '*';
@@ -180,6 +194,7 @@ my $html_init = qq!
     </FORM>
     <BR><BR>
   !;
+$html_init .= include('.style');
 
 $cgi->param('dummy', 1);
 
@@ -562,6 +577,43 @@ if ( $taxclasses ) {
   push @header, 'Taxclass';
   push @fields, sub { shift->taxclass() || '&nbsp;'; };
   $align .= 'l';
+} elsif ( $taxvendor ) {
+  push @header, 'Tax product';
+  my @classnums = ( 'setup', 'recur' );
+  my @classnames = ( 'Setup', 'Recur' );
+  foreach ( qsearch('usage_class', { disabled => '' }) ) {
+    push @classnums, $_->classnum;
+    push @classnames, $_->classname;
+  }
+  my $taxproduct_sub = sub {
+    my $ppt = shift;
+    '<SPAN CLASS="taxproduct_desc" TITLE="' .
+      encode_entities($ppt->description) .
+    '">' . encode_entities($ppt->taxproduct) . '</SPAN>'
+  };
+  my $taxproduct_list_sub = sub {
+    my $part_pkg = shift;
+    my $base_ppt = $part_pkg->taxproduct;
+    my $out = [];
+    if ( $base_ppt ) {
+      push @$out, [
+        { 'data'  => '', 'align' => 'left' },
+        { 'data'  => &$taxproduct_sub($base_ppt), 'align' => 'right' },
+      ];
+    }
+    for (my $i = 0; $i < scalar @classnums; $i++) {
+      my $num = $part_pkg->option('usage_taxproductnum_' . $classnums[$i]);
+      next if !$num;
+      my $ppt = FS::part_pkg_taxproduct->by_key($num);
+      push @$out, [
+        { 'data'  => $classnames[$i] . ': ', 'align' => 'left', },
+        { 'data'  => &$taxproduct_sub($ppt), 'align' => 'right' },
+      ];
+    }
+    $out;
+  };
+  push @fields, $taxproduct_list_sub;
+  $align .= 'l';
 }
 
 # make a table of report class optionnames =>  the actual 
@@ -602,7 +654,9 @@ push @fields,
                         sort
                         grep { $options{$_} =~ /\S/ } 
                         grep { $_ !~ /^(setup|recur)_fee$/ 
-                               and $_ !~ /^report_option_\d+$/ }
+                               and $_ !~ /^report_option_\d+$/
+                               and $_ !~ /^usage_taxproductnum_/
+                             }
                         keys %options
                       );
                       if ( @report_options ) {
index 9c00c5c..8f237f3 100755 (executable)
@@ -138,8 +138,7 @@ my $id = $cgi->param('id');
 my $select_onclick = sub {
   my $row = shift;
   my $taxnum = $row->taxproductnum;
-  my $code = $row->taxproduct;
-  my $desc = $row->description;
+  my $desc = $row->taxproduct . ' ' . $row->description;
   "select_taxproduct('$taxnum', '$desc')";
 };
 
index 5feb71d..d08ac22 100644 (file)
@@ -1,14 +1,79 @@
-<% $opt{'prefix'} %><INPUT NAME    = "<% $name %>"
-                           ID      = "<% $name %>"
-                           TYPE    = "hidden"
-                           VALUE   = "<% $value |h %>"
-                    >
-                    <INPUT NAME    = "<% $name %>_description"
-                           ID      = "<% $name %>_description"
-                           TYPE    = "text"
-                           VALUE   = "<% $description %>"
-                           SIZE    = "12"
-                           onClick = "<% $onclick %>"><% $opt{'postfix'} %>
+% if (!$init) {
+%   $init = 1;
+<STYLE TYPE="text/css">
+.ui-autocomplete-loading {
+  background-color: silver;
+}
+ul.ui-autocomplete li.ui-menu-item {
+  font-size: 0.8em;
+  padding: 2px;
+}
+ul.ui-autocomplete li.ui-state-focus {
+  font-weight: normal;
+  color: #7e0079;
+  background-color: inherit;
+  border: 1px solid #7e0079;
+}
+</STYLE>
+<SCRIPT TYPE="text/javascript">
+$().ready(function() {
+  $('input.taxproduct_desc').autocomplete({
+    source: '<% $fsurl %>misc/taxproduct.cgi',
+    minLength: 3,
+    autoFocus: true,
+    response: function( event, ui ) {
+      // if there's only one choice (user entered an exact taxproduct) then
+      // select it
+      if ( ui.content.length == 1 ) {
+        var input_taxproductnum = $(this).siblings('.taxproductnum')
+        var item = ui.content[0];
+        $(this).val(item.label);
+        input_taxproductnum.val(item.value);
+      }
+    },
+    focus: function( event, ui ) {
+      return false;
+    },
+    select: function( event, ui ) {
+      // find the hidden input for the taxproductnum
+      var input_taxproductnum = $(this).siblings('.taxproductnum')
+      if ( ui.item ) {
+        $(this).val(ui.item.label);
+        input_taxproductnum.val(ui.item.value);
+        return false; // don't store item.value in this input
+      } else {
+        input_taxproductnum.val('');
+      }
+    },
+    change: function( event, ui ) {
+      var input_taxproductnum = $(this).siblings('.taxproductnum')
+      if ( $(this).val() == '' ) {
+        input_taxproductnum.val('');
+      }
+    }
+  });
+});
+</SCRIPT>
+% }
+<% $opt{'prefix'} %>
+<INPUT NAME    = "<% $name %>"
+       ID      = "<% $name %>"
+       TYPE    = "hidden"
+       VALUE   = "<% $value |h %>"
+       CLASS   = "taxproductnum"
+>
+<INPUT NAME    = "<% $name %>_description"
+       ID      = "<% $name %>_description"
+       TYPE    = "text"
+       VALUE   = "<% $description %>"
+       SIZE    = "50"
+       CLASS   = "taxproduct_desc" 
+>
+<BUTTON STYLE="padding: 0" onclick="<% $onclick %>">...</BUTTON>
+<% $opt{'postfix'} %>
+<%shared>
+my $init = 0;
+</%shared>
 <%init>
 
 my %opt = @_;
@@ -19,7 +84,8 @@ my $description = $opt{'taxproduct_description'};
 unless ( $description || ! $value ) {
     my $part_pkg_taxproduct =
       qsearchs( 'part_pkg_taxproduct', { 'taxproductnum'=> $value } );
-    $description = $part_pkg_taxproduct->description
+    $description = $part_pkg_taxproduct->taxproduct . ' ' .
+                   $part_pkg_taxproduct->description
       if $part_pkg_taxproduct;
 }
 
index c57232c..5dcea09 100644 (file)
@@ -1,18 +1,39 @@
+%# by default, only show default
 <TR>
-  <TH COLSPAN=2>Tax products</TH>
+  <TH ALIGN="right"><% emt('Tax product') %></TH>
+  <TD><& select-taxproduct.html,
+        %opt,
+        'field'       => $field.'_', # expected in edit/process/part_pkg
+        'curr_value'  => $curr_values{''},
+      &>
+  </TD>
+</TR>
+% if ( !$separate ) {
+<TR STYLE="font-size: small">
+  <TD></TD>
+  <TD>
+  <BUTTON TYPE="button" ID="show_taxproduct_class">By usage class
+    <IMG SRC="<%$fsurl%>images/arrow.down.black.png">
+  </BUTTON>
+  </TD>
 </TR>
+<SCRIPT TYPE="text/javascript">
+$().ready(function() {
+  $('#show_taxproduct_class').on('click', function() {
+    this.disabled = true;
+    $('tr.taxproduct_class').show();
+  });
+});
+</SCRIPT>
+% }
 % foreach my $usage_class (@classes) {
 %   my $classnum = $usage_class->classnum;
-%   my $curr_value =
-%        $cgi->param("usage_taxproductnum_$classnum")
-%     || $pkg_options{"usage_taxproductnum_$classnum"}
-%     || '';
-<TR>
-  <TD><% $usage_class->classname %></TD>
+<TR CLASS="taxproduct_class" STYLE="<% $separate ? '' : 'display:none' %>">
+  <TH ALIGN="right"><% $usage_class->classname %></TH>
   <TD><& select-taxproduct.html,
         %opt,
         'field'       => $field.'_'.$classnum,
-        'curr_value'  => $curr_value
+        'curr_value'  => $curr_values{$classnum},
       &>
   </TD>
 </TR>
 <%init>
 my %opt = @_;
 my $field = delete($opt{field}) || 'taxproductnum';
+my @classes = qsearch('usage_class', { 'disabled' => '' });
+unshift @classes,
+  FS::usage_class->new({ 'classnum' => 'setup', 'classname' => 'Setup', }),
+  FS::usage_class->new({ 'classnum' => 'recur', 'classname' => 'Recur', }),
+;
+my $separate = 0; # will change to 1 if any non-default classes have values
+
 my $pkgpart = delete($opt{pkgpart});
+my %curr_values;
 my %pkg_options;
 if ($pkgpart) {
   my $part_pkg = FS::part_pkg->by_key($pkgpart);
   %pkg_options = $part_pkg->options;
-  $pkg_options{'usage_taxproductnum_'} = $part_pkg->taxproductnum;
+  $curr_values{''} = $part_pkg->taxproductnum;
 }
 
-my @classes = qsearch('usage_class', { 'disabled' => '' });
-unshift @classes,
-  FS::usage_class->new({ 'classnum' => '', 'classname' => '(default)', }),
-  FS::usage_class->new({ 'classnum' => 'setup', 'classname' => 'Setup', }),
-  FS::usage_class->new({ 'classnum' => 'recur', 'classname' => 'Recur', }),
-;
+foreach my $usage_class (@classes) {
+  my $classnum = $usage_class->classnum;
+  my $curr_value =
+       $cgi->param("usage_taxproductnum_$classnum")
+    || $pkg_options{"usage_taxproductnum_$classnum"}
+    || '';
+  $curr_values{$classnum} = $curr_value;
+  $separate = 1 if ( length($classnum) and length($curr_value) );
+}
 </%init>
diff --git a/httemplate/misc/taxproduct.cgi b/httemplate/misc/taxproduct.cgi
new file mode 100644 (file)
index 0000000..b228493
--- /dev/null
@@ -0,0 +1,24 @@
+<%once>
+my $conf = FS::Conf->new;
+my $vendor = $conf->config('tax_data_vendor');
+</%once>
+<%init>
+my $term = $cgi->param('term');
+warn "taxproduct.cgi?$term"; # XXX debug
+my $search = { table => 'part_pkg_taxproduct' };
+if ( $term =~ /^\d+$/ ) {
+  $search->{extra_sql} = " WHERE taxproduct LIKE '$term%'";
+  $search->{order_by} = " ORDER BY taxproduct ASC";
+} elsif ( length($term) ) {
+  $term = dbh->quote( lc($term) ); # protect against bad strings
+  $search->{extra_sql} = " WHERE POSITION($term IN LOWER(description)) > 0";
+  # and sort by how close to the beginning of the string it is
+  $search->{order_by} = " ORDER BY POSITION($term IN LOWER(description)) ASC, LOWER(description) ASC, taxproduct ASC";
+}
+my @taxproducts = qsearch($search);
+my @results = map {
+  { label => $_->taxproduct . ' ' . $_->description,
+    value => $_->taxproductnum } 
+} @taxproducts;
+</%init>
+<% encode_json(\@results) %>\