FCC form 477 reporting #4912
authorjeff <jeff>
Mon, 29 Jun 2009 13:53:25 +0000 (13:53 +0000)
committerjeff <jeff>
Mon, 29 Jun 2009 13:53:25 +0000 (13:53 +0000)
23 files changed:
FS/FS.pm
FS/FS/Conf.pm
FS/FS/Schema.pm
FS/FS/cust_main.pm
FS/FS/cust_pkg.pm
FS/FS/part_pkg_report_option.pm [new file with mode: 0644]
FS/MANIFEST
FS/t/part_pkg_report_option.t [new file with mode: 0644]
httemplate/browse/part_pkg_report_option.html [new file with mode: 0644]
httemplate/edit/cust_main/bottomfixup.html
httemplate/edit/cust_main/bottomfixup.js
httemplate/edit/cust_main/choose_tax_location.html
httemplate/edit/cust_main/contact.html
httemplate/edit/part_pkg.cgi
httemplate/edit/part_pkg_report_option.html [new file with mode: 0644]
httemplate/edit/process/part_pkg.cgi
httemplate/edit/process/part_pkg_report_option.html [new file with mode: 0644]
httemplate/elements/location.html
httemplate/elements/menu.html
httemplate/misc/xmlhttp-cust_main-censustract.html [new file with mode: 0644]
httemplate/search/cust_main.html
httemplate/search/report_cust_main.html
httemplate/search/report_cust_pkg.html

index 1477e98..c314873 100644 (file)
--- a/FS/FS.pm
+++ b/FS/FS.pm
@@ -167,9 +167,9 @@ L<FS::part_export> - External provisioning export class
 
 L<FS::part_export_option> - Export option class
 
-L<FS::pkg_category> - Package category class
+L<FS::pkg_category> - Package category class (invoice oriented)
 
-L<FS::pkg_class> - Package class class
+L<FS::pkg_class> - Package class class (tax oriented)
 
 L<FS::part_pkg> - Package definition class
 
@@ -179,6 +179,8 @@ L<FS::part_pkg_taxclass> - Tax class class
 
 L<FS::part_pkg_option> - Package definition option class
 
+L<FS::part_pkg_report_option> - Package reporting classification class
+
 L<FS::pkg_svc> - Class linking package definitions (see L<FS::part_pkg>) with
 service definitions (see L<FS::part_svc>)
 
index b45cdd7..12d6075 100644 (file)
@@ -2576,6 +2576,13 @@ worry that config_items is freeside-specific and icky.
   },
 
   {
+    'key'         => 'cust_main-require_censustract',
+    'section'     => 'UI',
+    'description' => 'Customer is required to have a census tract.  Useful for FCC form 477 reports. See also: cust_main-auto_standardize_address',
+    'type'        => 'checkbox',
+  },
+
+  {
     'key'         => 'disable_acl_changes',
     'section'     => '',
     'description' => 'Disable all ACL changes, for demos.',
index aed8d60..61cd17e 100644 (file)
@@ -686,6 +686,7 @@ sub tables_hashref {
         'paytype',  'varchar', 'NULL', $char_d, '', '', 
         'payip',    'varchar', 'NULL', 15, '', '', 
         'geocode',  'varchar', 'NULL', 20,  '', '',
+        'censustract', 'varchar', 'NULL', 20,  '', '', # 7 to save space?
         'tax',      'char', 'NULL', 1, '', '', 
         'otaker',   'varchar', '',    32, '', '', 
         'refnum',   'int',  '',     '', '', '', 
@@ -1850,6 +1851,17 @@ sub tables_hashref {
       'index'       => [ [ 'pkgpart' ], [ 'optionname' ] ],
     },
 
+    'part_pkg_report_option' => {
+      'columns' => [
+        'num',      'serial',   '',      '', '', '', 
+        'name',     'varchar',  '', $char_d, '', '', 
+        'disabled', 'char', 'NULL',       1, '', '', 
+      ],
+      'primary_key' => 'num',
+      'unique' => [ [ 'name' ] ],
+      'index' => [ [ 'disabled' ] ],
+    },
+
     'rate' => {
       'columns' => [
         'ratenum',  'serial', '', '', '', '', 
index 3f23346..e5f289c 100644 (file)
@@ -7573,6 +7573,13 @@ sub search_sql {
     unless $params->{'cancelled_pkgs'};
 
   ##
+  # parse without census tract checkbox
+  ##
+
+  push @where, "(censustract = '' or censustract is null)"
+    if $params->{'no_censustract'};
+
+  ##
   # dates
   ##
 
index a510c52..881e005 100644 (file)
@@ -2325,12 +2325,44 @@ sub search_sql {
   #eslaf
 
   ###
+  # parse package report options
+  ###
+
+  my @report_option = ();
+  if ( exists($params->{'report_option'})
+       && $params->{'report_option'} =~ /^([,\d]*)$/
+     )
+  {
+    @report_option = split(',', $1);
+  }
+
+  if (@report_option) {
+    # this will result in the empty set for the dangling comma case as it should
+    push @where, 
+      map{ "0 < ( SELECT count(*) FROM part_pkg_option
+                    WHERE part_pkg_option.pkgpart = part_pkg.pkgpart
+                    AND optionname = 'report_option_$_'
+                    AND optionvalue = '1' )"
+         } @report_option;
+  }
+
+  #eslaf
+
+  ###
   # parse custom
   ###
 
   push @where,  "part_pkg.custom = 'Y'" if $params->{custom};
 
   ###
+  # parse censustract
+  ###
+
+  if ( $params->{'censustract'} =~ /^([.\d]+)$/ and $1 ) {
+    push @where,  "cust_main.censustract = '". $params->{censustract}. "'";
+  }
+
+  ###
   # parse part_pkg
   ###
 
diff --git a/FS/FS/part_pkg_report_option.pm b/FS/FS/part_pkg_report_option.pm
new file mode 100644 (file)
index 0000000..16a4c98
--- /dev/null
@@ -0,0 +1,125 @@
+package FS::part_pkg_report_option;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::part_pkg_report_option - Object methods for part_pkg_report_option records
+
+=head1 SYNOPSIS
+
+  use FS::part_pkg_report_option;
+
+  $record = new FS::part_pkg_report_option \%hash;
+  $record = new FS::part_pkg_report_option { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_pkg_report_option object represents a package definition optional
+reporting classification.  FS::part_pkg_report_option inherits from
+FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item num
+
+primary key
+
+=item name
+
+name - The name associated with the reporting option
+
+=item disabled
+
+disabled - set to 'Y' to prevent addition to new packages
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new report option.  To add the option to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'part_pkg_report_option'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+sub delete {
+  return "Can't delete part_pkg_report_option records!";
+}
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+=item check
+
+Checks all fields to make sure this is a valid example.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('num')
+    || $self->ut_text('name')
+    || $self->ut_enum('disabled', [ '', 'Y' ])
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+Overlaps somewhat with pkg_class and pkg_category
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
index 70287a3..7def8df 100644 (file)
@@ -439,6 +439,8 @@ FS/cust_bill_pkg_tax_rate_location.pm
 t/cust_bill_pkg_tax_rate_location.t
 FS/cust_recon.pm
 t/cust_recon.t
+FS/part_pkg_report_option.pm
+t/part_pkg_report_option.t
 FS/cust_main_exemption.pm
 t/cust_main_exemption.t
 FS/cust_tax_adjustment.pm
diff --git a/FS/t/part_pkg_report_option.t b/FS/t/part_pkg_report_option.t
new file mode 100644 (file)
index 0000000..622bb38
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg_report_option;
+$loaded=1;
+print "ok 1\n";
diff --git a/httemplate/browse/part_pkg_report_option.html b/httemplate/browse/part_pkg_report_option.html
new file mode 100644 (file)
index 0000000..90540bc
--- /dev/null
@@ -0,0 +1,28 @@
+<% include( 'elements/browse.html',
+                 'title'       => 'Package optional report classes',
+                 'html_init'   => $html_init,
+                 'name'        => 'package optional report classes',
+                 'disableable' => 1,
+                 'disabled_statuspos' => 2,
+                 'query'       => { 'table'     => 'part_pkg_report_option',
+                                    'hashref'   => {},
+                                    'extra_sql' => 'ORDER BY name',
+                                  },
+                 'count_query' => 'SELECT COUNT(*) FROM part_pkg_report_option',
+                 'header'      => [ '#', 'Class' ],
+                 'fields'      => [ 'num', 'name' ],
+                 'links'       => [ $link, $link ],
+             )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init = 
+  'Package optional report classes define groups of packages, for reporting purposes.'.
+  qq!<BR><BR><A HREF="${p}edit/part_pkg_report_option.html"><I>Add a class</I></A><BR><BR>!;
+
+my $link = [ $p.'edit/part_pkg_report_option.html?', 'num' ];
+
+</%init>
index 3eb43e0..1b29c67 100644 (file)
@@ -7,6 +7,13 @@
           )
 %>
 
+<% include( '/elements/xmlhttp.html',
+              'url'  => $p.'misc/xmlhttp-cust_main-censustract.html',
+              'subs' => [ 'censustract' ],
+              #'method' => 'POST', #could get too long?
+          )
+%>
+
 <SCRIPT TYPE="text/javascript">
   <% include('bottomfixup.js') %>
 </SCRIPT>
index ae4aafb..822f98d 100644 (file)
@@ -86,52 +86,17 @@ function update_address(arg) {
       cf.elements['ship_zip'].value      = argsHash['new_ship_zip'];
     }
 
-  }
-
-  var cf = document.CustomerForm;
-
-% if ( $conf->exists('enable_taxproducts') ) {
-
-  if ( <% $taxpre %>error ||
-       new String(argsHash['new_<% $taxpre %>zip']).length < 10 )
-  {
-
-    var country_el = cf.elements['<% $taxpre %>country'];
-    var country = country_el.options[ country_el.selectedIndex ].value;
-
-    if ( country == 'CA' || country == 'US' ) {
-
-      var state_el = cf.elements['<% $taxpre %>state'];
-      var state = state_el.options[ state_el.selectedIndex ].value;
-
-      var url = "cust_main/choose_tax_location.html" +
-                  "?data_vendor=cch-zip" + 
-                  ";city="    + cf.elements['<% $taxpre %>city'].value +
-                  ";state="   + state + 
-                  ";zip="     + cf.elements['<% $taxpre %>zip'].value +
-                  ";country=" + country +
-                  ";";
-
-      // popup a chooser
-      OLgetAJAX( url, update_geocode, 300 );
-
-    } else {
-
-      cf.elements['geocode'].value = 'DEFAULT';
-      cf.submit();
+    post_standardization();
 
-    }
+  }
 
-  } else
 
-% }
 
   if ( changed || ship_changed ) {
 
 %   if ( $conf->exists('cust_main-auto_standardize_address') ) {
 
     standardize_address();
-    cf.submit();
 
 %   } else {
 
@@ -198,9 +163,9 @@ function update_address(arg) {
 
     confirm_change = confirm_change +
       '<TR><TD>' +
-        '<BUTTON TYPE="button" onClick="document.CustomerForm.submit();"><IMG SRC="<%$p%>images/error.png" ALT=""> Use entered ' + addresses + '</BUTTON>' + 
+        '<BUTTON TYPE="button" onClick="post_standardization();"><IMG SRC="<%$p%>images/error.png" ALT=""> Use entered ' + addresses + '</BUTTON>' + 
       '</TD><TD>' +
-        '<BUTTON TYPE="button" onClick="standardize_address(); document.CustomerForm.submit();"><IMG SRC="<%$p%>images/tick.png" ALT=""> Use standardized ' + addresses + '</BUTTON>' + 
+        '<BUTTON TYPE="button" onClick="standardize_address();"><IMG SRC="<%$p%>images/tick.png" ALT=""> Use standardized ' + addresses + '</BUTTON>' + 
       '</TD></TR>' +
       '<TR><TD COLSPAN=2 ALIGN="center">' +
         '<BUTTON TYPE="button" onClick="document.CustomerForm.submitButton.disabled=false; parent.cClick();"><IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission</BUTTON></TD></TR>' +
@@ -213,10 +178,85 @@ function update_address(arg) {
 
   } else {
 
-    cf.submit();
+    post_standardization();
+
+  }
+
+
+}
+
+function post_standardization() {
+
+  var cf = document.CustomerForm;
+
+% if ( $conf->exists('enable_taxproducts') ) {
+
+  if ( new String(cf.elements['<% $taxpre %>zip'].value).length < 10 )
+  {
+
+    var country_el = cf.elements['<% $taxpre %>country'];
+    var country = country_el.options[ country_el.selectedIndex ].value;
+
+    if ( country == 'CA' || country == 'US' ) {
+
+      var state_el = cf.elements['<% $taxpre %>state'];
+      var state = state_el.options[ state_el.selectedIndex ].value;
+
+      var url = "cust_main/choose_tax_location.html" +
+                  "?data_vendor=cch-zip" + 
+                  ";city="    + cf.elements['<% $taxpre %>city'].value +
+                  ";state="   + state + 
+                  ";zip="     + cf.elements['<% $taxpre %>zip'].value +
+                  ";country=" + country +
+                  ";";
+
+      // popup a chooser
+      OLgetAJAX( url, update_geocode, 300 );
+
+    } else {
+
+      cf.elements['geocode'].value = 'DEFAULT';
+      post_geocode();
+
+    }
+
+  } else {
+
+    post_geocode();
 
   }
 
+% } else {
+
+  post_geocode();
+
+% }
+
+}
+
+function post_geocode() {
+
+% if ( $conf->exists('cust_main-require_censustract') ) {
+
+  //alert('fetch census tract data');
+  var cf = document.CustomerForm;
+  var state_el = cf.elements['ship_state'];
+  var census_data = new Array(
+    'year',    '2008', // from config value?
+    'address', cf.elements['ship_address1'].value,
+    'city',    cf.elements['ship_city'].value,
+    'state',   state_el.options[ state_el.selectedIndex ].value,
+    'zip',     cf.elements['ship_zip'].value
+  );
+
+  censustract( census_data, update_censustract );
+
+% }else{
+
+  document.CustomerForm.submit();
+
+% }
+
 }
 
 function update_geocode() {
@@ -232,6 +272,7 @@ function update_geocode() {
     setselect(cf.elements['<% $taxpre %>state'], argsHash['state']);
     cf.elements['<% $taxpre %>zip'].value      = argsHash['zip'];
     cf.elements['geocode'].value  = argsHash['geocode'];
+    post_geocode();
 
   }
 
@@ -241,6 +282,64 @@ function update_geocode() {
 
 }
 
+var set_censustract;
+
+function update_censustract(arg) {
+
+  var argsHash = eval('(' + arg + ')');
+
+  var cf = document.CustomerForm;
+
+  var msacode    = argsHash['msacode'];
+  var statecode  = argsHash['statecode'];
+  var countycode = argsHash['countycode'];
+  var tractcode  = argsHash['tractcode'];
+  var error      = argsHash['error'];
+  
+  set_censustract = function () {
+
+    cf.elements['censustract'].value =
+      document.forms.popupcensustract.elements.censustract.value;
+    cf.submit();
+
+  }
+
+  if (error) {
+    // popup an entry dialog
+
+    var choose_censustract =
+      '<CENTER><BR><B>Enter census tract</B><BR><BR>' + 
+      '<FORM name="popupcensustract">' +
+      '<TABLE>';
+    
+    choose_censustract = choose_censustract + 
+      '<TR><TH>Census Tract: </TH>' +
+        '<TD><INPUT NAME="censustract" ID="censustract"></TD>' +
+      '</TR><TR>' +
+        '<TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
+      
+    choose_censustract = choose_censustract + 
+      '<TR><TD>' +
+        '<BUTTON TYPE="button" onClick="set_censustract();"><IMG SRC="<%$p%>images/tick.png" ALT="">Submit census tract</BUTTON>' + 
+      '</TD><TD>' +
+        '<BUTTON TYPE="button" onClick="document.CustomerForm.submitButton.disabled=false; parent.cClick();"><IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission</BUTTON></TD></TR>' +
+      '</TABLE></FORM></CENTER>';
+
+      overlib( choose_censustract, CAPTION, 'Choose a census tract', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399', TEXTSIZE, 3 );
+
+      setTimeout("document.forms.popupcensustract.elements.censustract.focus()",1);
+  } else {
+
+    cf.elements['censustract'].value =
+      new String(statecode)  +
+      new String(countycode) +
+      new String(tractcode);
+    cf.submit();
+
+  }
+
+}
+
 function copyelement(from, to) {
   if ( from == undefined ) {
     to.value = '';
index 2a41926..be93a5d 100644 (file)
@@ -26,7 +26,7 @@
 </SELECT><BR><BR>
 
 <TABLE><TR>
-  <TD> <BUTTON TYPE="button" onClick="set_geocode(document.getElementById('geocodes')); document.CustomerForm.submit();"><IMG SRC="<%$p%>images/tick.png" ALT=""> Set location </BUTTON></TD>
+  <TD> <BUTTON TYPE="button" onClick="set_geocode(document.getElementById('geocodes'));"><IMG SRC="<%$p%>images/tick.png" ALT=""> Set location </BUTTON></TD>
   <TD><BUTTON TYPE="button" onClick="document.CustomerForm.submitButton.disabled=false; parent.cClick();"><IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission </BUTTON></TD>
 </TR>
 </TABLE>
index 27dd385..2691da6 100644 (file)
@@ -32,6 +32,7 @@
              'disabled'     => $disabled,
              'same_checked' => $opt{'same_checked'},
              'geocode'      => $opt{'geocode'},
+             'censustract'  => $opt{'censustract'},
           )
 %>
 
index 0934f50..a78aa87 100755 (executable)
@@ -46,6 +46,7 @@
                             'recur_fee'        => 'Recurring fee',
                             'bill_dst_pkgpart' => 'Include line item(s) from package',
                             'svc_dst_pkgpart'  => 'Include services of package',
+                            'report_option'    => 'Report classes',
                           },
 
               'fields' => [
 
                             { type => 'columnend' },
 
+                            { 'type'  => $census ? 'tablebreak-tr-title'
+                                                 : 'hidden',
+                              'value' => 'Optional report classes',
+                              'field' => 'census_title',
+                            },
+                            { 'field'    => 'report_option',
+                              'type'     => $census ? 'select-table' : 'hidden',
+                              'table'    => 'part_pkg_report_option',
+                              'name_col' => 'name',
+                              'multiple' => 1,
+                            },
+
+
                             { 'type'  => 'tablebreak-tr-title',
                               'value' => 'Pricing add-ons',
                             },
@@ -224,6 +238,7 @@ my $agent_clone_extra_sql =
 
 my $conf = new FS::Conf;
 my $taxproducts = $conf->exists('enable_taxproducts');
+my $census = scalar( qsearch( 'part_pkg_report_option', {} ) );
 
 #XXX
 # - tr-part_pkg_freq: month_increments_only (from price plans)
@@ -301,14 +316,27 @@ my $edit_callback = sub {
 
   (@agent_type) = map {$_->typenum} qsearch('type_pkgs',{'pkgpart'=>$1});
 
+  my @report_option = ();
   foreach ($object->options) {
     /^usage_taxproductnum_(\d+)$/ && ($taxproductnums{$1} = 1);
+    /^report_option_(\d+)$/ && (push @report_option, $1);
   }
   foreach ($object->part_pkg_taxoverride) {
     $taxproductnums{$_->usage_class} = 1
       if $_->usage_class;
   }
 
+  $cgi->param('report_option', join(',', @report_option));
+  foreach my $field ( @$fields ) {
+    next unless ( 
+      ref($field) eq 'HASH' &&
+      $field->{field} &&
+      $field->{field} eq 'report_option'
+    );
+    #$field->{curr_value} = join(',', @report_option);
+    $field->{value} = join(',', @report_option);
+  }
+
   %options = $object->options;
 
   $object->set($_ => $object->option($_))
diff --git a/httemplate/edit/part_pkg_report_option.html b/httemplate/edit/part_pkg_report_option.html
new file mode 100644 (file)
index 0000000..a6f8e57
--- /dev/null
@@ -0,0 +1,23 @@
+<% include( 'elements/edit.html',
+              'name'   => 'Package optional report class',
+              'table'  => 'part_pkg_report_option',
+              'fields' => [
+                            'name',
+                            { field=>'num', type=>'hidden' },
+                            { field=>'disabled', type=>'checkbox', value=>'Y', },
+                          ],
+              'labels' => { 
+                            'num'      => 'Class number',
+                            'name'     => 'Class name',
+                            'disabled' => 'Disable class',
+                          },
+              'viewall_dir' => 'browse',
+           )
+          
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
index 3116b7b..107d459 100755 (executable)
@@ -102,6 +102,11 @@ my $args_callback = sub {
     $options{"usage_taxproductnum_$_"} = $value;
   }
 
+  foreach ( split(',', $cgi->param('report_option') ) ) {
+    $error ||= "Illegal optional report class: $_" unless ( $_ =~ /^\d*$/  );
+    $options{"report_option_$_"} = 1;
+  }
+
   $options{$_} = scalar( $cgi->param($_) )
     for (qw( setup_fee recur_fee ));
   
diff --git a/httemplate/edit/process/part_pkg_report_option.html b/httemplate/edit/process/part_pkg_report_option.html
new file mode 100644 (file)
index 0000000..052aabd
--- /dev/null
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+               'table'       => 'part_pkg_report_option',
+               'viewall_dir' => 'browse',
+           )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
index dbc567d..1bdbf96 100644 (file)
@@ -9,6 +9,7 @@ Example:
              'disabled'       => $disabled,
              'same_checked'   => $same_checked,
              'geocode'        => $geocode, #passed through
+             'censustract'    => $censustract, #passed through
              'no_asterisks'   => 0, #set true to disable the red asterisks next
                                     #to required fields
              'address1_label' => 'Address', #label for address
@@ -85,6 +86,8 @@ Example:
 
 % if ( !$pre ) { 
   <INPUT TYPE="hidden" NAME="geocode" VALUE="<% $opt{geocode} %>">
+% } else {
+  <INPUT TYPE="hidden" NAME="censustract" VALUE="<% $opt{censustract} %>">
 % } 
 
 <%init>
index 5789a8a..cda1efc 100644 (file)
@@ -170,6 +170,8 @@ if ( $curuser->access_right('Financial reports') ) {
 $report_packages{'All customer packages'} =  [ $fsurl.'search/cust_pkg.cgi?pkgnum', 'List all customer packages', ];
 $report_packages{'Suspended customer packages'} =  [ $fsurl.'search/cust_pkg.cgi?magic=suspended', 'List suspended packages' ];
 $report_packages{'Customer packages with unconfigured services'} =  [ $fsurl.'search/cust_pkg.cgi?APKG_pkgnum', 'List packages which have provisionable services' ];
+$report_packages{'FCC Form 477 packages'} =  [ $fsurl.'search/report_477.html', 'Summarize packages by census tract for particular types' ]
+  if $conf->exists('cust_main-require_censustract');
 $report_packages{'Advanced package reports'} =  [ $fsurl.'search/report_cust_pkg.html', 'by agent, date range, status, package definition' ];
 
 tie my %report_rating, 'Tie::IxHash',
@@ -284,7 +286,8 @@ $config_export_svc_pkg{'View/Edit package definitions'} = [ $fsurl.'browse/part_
      || $curuser->access_right('Edit global package definitions');
 if ( $curuser->access_right('Configuration') ) {
   $config_export_svc_pkg{'View/Edit package categories'} =  [ $fsurl.'browse/pkg_category.html', 'Package categories define groups of package classes, for reporting and convenience purposes.' ];
-  $config_export_svc_pkg{'View/Edit package classes'} =  [ $fsurl.'browse/pkg_class.html', 'Package classes define groups of packages, for reporting and convenience purposes.' ];
+  $config_export_svc_pkg{'View/Edit package tax classes'} =  [ $fsurl.'browse/pkg_class.html', 'Package classes define groups of packages, for taxes, reporting and convenience purposes.' ];
+  $config_export_svc_pkg{'View/Edit package report classes'} =  [ $fsurl.'browse/part_pkg_report_option.html', 'Package classes define groups of packages for reporting purposes.' ];
   $config_export_svc_pkg{'View/Edit cancel reason types'} = [ $fsurl.'browse/reason_type.html?class=C', 'Cancel reason types define groups of reasons, for reporting and convenience purposes.' ];
   $config_export_svc_pkg{'View/Edit cancel reasons'} = [ $fsurl.'browse/reason.html?class=C', 'Cancel reasons explain why a service was cancelled.' ];
   $config_export_svc_pkg{'View/Edit suspend reason types'} = [ $fsurl.'browse/reason_type.html?class=S', 'Suspend reason types define groups of reasons, for reporting and convenience purposes.' ];
diff --git a/httemplate/misc/xmlhttp-cust_main-censustract.html b/httemplate/misc/xmlhttp-cust_main-censustract.html
new file mode 100644 (file)
index 0000000..05636d3
--- /dev/null
@@ -0,0 +1,103 @@
+<% objToJson($return) %>
+<%init>
+
+my $DEBUG = 0;
+
+my $url='http://www.ffiec.gov/Geocode/default.aspx';
+
+my $sub = $cgi->param('sub');
+
+my $return = {};
+my $error = '';
+
+use LWP::UserAgent;
+use HTTP::Request;
+use HTTP::Request::Common qw( GET POST );
+use HTML::TokeParser;
+
+if ( $sub eq 'censustract' ) {
+
+  my %arg = $cgi->param('arg');
+  warn join('', map "$_: $arg{$_}\n", keys %arg )
+    if $DEBUG;
+
+  my $ua = new LWP::UserAgent;
+  my $res = $ua->request( GET( $url ) );
+
+  warn $res->as_string
+    if $DEBUG > 1;
+
+  unless ($res->code  eq '200') {
+
+    $error = $res->message;
+
+  } else {
+
+    my $content = $res->content;
+    my $p = new HTML::TokeParser \$content;
+    my $viewstate;
+    while (my $token = $p->get_tag('input') ) {
+      next unless $token->[1]->{name} eq '__VIEWSTATE';
+      $viewstate = $token->[1]->{value};
+      last;
+    }
+
+    unless ($viewstate) {
+
+      $error = "no __VIEWSTATE found";
+
+    } else {
+
+      my($zip5, $zip4) = split('-',$arg{zip});
+
+      my @ffiec_args = (
+        __VIEWSTATE => $viewstate,
+        ddlbYear    => $arg{year},
+        txtAddress  => $arg{address},
+        txtCity     => $arg{city},  
+        ddlbState   => $arg{state},
+        txtZipCode  => $zip5,
+        btnSearch   => 'Search',
+      );
+      warn join("\n", @ffiec_args )
+        if $DEBUG;
+
+      $res = $ua->request( POST( $url, \@ffiec_args ) );
+      warn $res->as_string
+        if $DEBUG > 1;
+
+      unless ($res->code  eq '200') {
+
+        $error = $res->message;
+
+      } else {
+
+        my @id = qw( MSACode StateCode CountyCode TractCode );
+        $content = $res->content;
+        $p = new HTML::TokeParser \$content;
+        my $prefix = 'UcGeoResult11_lb';
+        my $compare =
+          sub { my $t=shift; scalar( grep { lc($t) eq lc("$prefix$_")} @id ) };
+
+        while (my $token = $p->get_tag('span') ) {
+          next unless ( $token->[1]->{id} && &$compare( $token->[1]->{id} ) );
+          $token->[1]->{id} =~ /^$prefix(\w+)$/;
+          $return->{lc($1)} = $p->get_trimmed_text("/span");
+        }
+
+        $error = "No census tract found" unless $return->{tractcode};
+
+      } #unless ($res->code  eq '200')
+
+    } #unless ($viewstate)
+
+  } #unless ($res->code  eq '200')
+
+  $error = "FFIEC Geocoding error: $error" if $error;
+  $return->{'error'} = $error;
+
+  $return;
+
+}
+
+</%init>
index 0625a12..f098fd3 100755 (executable)
@@ -43,9 +43,12 @@ my %search_hash = ();
 #$search_hash{'query'} = $cgi->keywords;
 
 #scalars
-for my $param (qw(
+my @scalars = qw (
   agentnum status cancelled_pkgs cust_fields flattened_pkgs custbatch
-)) {
+  no_censustract
+);
+
+for my $param ( @scalars ) {
   $search_hash{$param} = scalar( $cgi->param($param) )
     if $cgi->param($param);
 }
index b0c5fde..f139d4b 100755 (executable)
         <TD><INPUT TYPE="checkbox" NAME="cancelled_pkgs"></TD>
     </TR>
 
+%   if ( $conf->exists('cust_main-require_censustract') ) {
+
+    <TR>
+      <TD ALIGN="right" VALIGN="center">Without census tract</TD>
+        <TD><INPUT TYPE="checkbox" NAME="no_censustract"></TD>
+    </TR>
+
+%   }
+
     <TR>
       <TH BGCOLOR="#e8e8e8" COLSPAN=2>&nbsp;</TH>
     </TR>
@@ -84,6 +93,8 @@ die "access denied"
            $FS::CurrentUser::CurrentUser->access_right('List packages')
          );;
 
+my $conf = new FS::Conf;
+
 </%init>
 <%once>
 
index 3840663..66dd7d1 100755 (executable)
                )
     %>
 
+%   if ( scalar( qsearch( 'part_pkg_report_option', { 'disabled' => '' } ) ) ) {
+
+    <% include( '/elements/tr-select-table.html',
+                   'label'        => 'Report classes',
+                   'table'        => 'part_pkg_report_option',
+                   'name_col'     => 'name',
+                   'hashref'      => { 'disabled' => '' },
+                   'element_name' => 'report_option',
+                   'multiple'     => 'multiple',
+               )
+    %>
+
+%   }
+
 %   foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) {
 
       <TR>