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::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
 
 
 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_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>)
 
 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.',
     '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,  '', '',
         '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',  '',     '', '', '', 
         'tax',      'char', 'NULL', 1, '', '', 
         'otaker',   'varchar', '',    32, '', '', 
         'refnum',   'int',  '',     '', '', '', 
@@ -1850,6 +1851,17 @@ sub tables_hashref {
       'index'       => [ [ 'pkgpart' ], [ 'optionname' ] ],
     },
 
       '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', '', '', '', '', 
     'rate' => {
       'columns' => [
         'ratenum',  'serial', '', '', '', '', 
index 3f23346..e5f289c 100644 (file)
@@ -7573,6 +7573,13 @@ sub search_sql {
     unless $params->{'cancelled_pkgs'};
 
   ##
     unless $params->{'cancelled_pkgs'};
 
   ##
+  # parse without census tract checkbox
+  ##
+
+  push @where, "(censustract = '' or censustract is null)"
+    if $params->{'no_censustract'};
+
+  ##
   # dates
   ##
 
   # dates
   ##
 
index a510c52..881e005 100644 (file)
@@ -2325,12 +2325,44 @@ sub search_sql {
   #eslaf
 
   ###
   #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 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
   ###
 
   # 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
 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
 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>
 <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'];
     }
 
       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();
 
   if ( changed || ship_changed ) {
 
 %   if ( $conf->exists('cust_main-auto_standardize_address') ) {
 
     standardize_address();
-    cf.submit();
 
 %   } else {
 
 
 %   } else {
 
@@ -198,9 +163,9 @@ function update_address(arg) {
 
     confirm_change = confirm_change +
       '<TR><TD>' +
 
     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>' +
       '</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>' +
       '</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 {
 
 
   } 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() {
 }
 
 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'];
     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 = '';
 function copyelement(from, to) {
   if ( from == undefined ) {
     to.value = '';
index 2a41926..be93a5d 100644 (file)
@@ -26,7 +26,7 @@
 </SELECT><BR><BR>
 
 <TABLE><TR>
 </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>
   <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'},
              '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',
                             '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' => [
                           },
 
               'fields' => [
 
                             { type => 'columnend' },
 
 
                             { 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',
                             },
                             { '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 $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)
 
 #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});
 
 
   (@agent_type) = map {$_->typenum} qsearch('type_pkgs',{'pkgpart'=>$1});
 
+  my @report_option = ();
   foreach ($object->options) {
     /^usage_taxproductnum_(\d+)$/ && ($taxproductnums{$1} = 1);
   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;
   }
 
   }
   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($_))
   %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;
   }
 
     $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 ));
   
   $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
              '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
              '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} %>">
 
 % if ( !$pre ) { 
   <INPUT TYPE="hidden" NAME="geocode" VALUE="<% $opt{geocode} %>">
+% } else {
+  <INPUT TYPE="hidden" NAME="censustract" VALUE="<% $opt{censustract} %>">
 % } 
 
 <%init>
 % } 
 
 <%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{'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',
 $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.' ];
      || $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.' ];
   $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
 #$search_hash{'query'} = $cgi->keywords;
 
 #scalars
-for my $param (qw(
+my @scalars = qw (
   agentnum status cancelled_pkgs cust_fields flattened_pkgs custbatch
   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);
 }
   $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>
 
         <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>
     <TR>
       <TH BGCOLOR="#e8e8e8" COLSPAN=2>&nbsp;</TH>
     </TR>
@@ -84,6 +93,8 @@ die "access denied"
            $FS::CurrentUser::CurrentUser->access_right('List packages')
          );;
 
            $FS::CurrentUser::CurrentUser->access_right('List packages')
          );;
 
+my $conf = new FS::Conf;
+
 </%init>
 <%once>
 
 </%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>
 %   foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) {
 
       <TR>