diff options
| author | Mark Wells <mark@freeside.biz> | 2014-07-31 22:54:08 -0700 | 
|---|---|---|
| committer | Mark Wells <mark@freeside.biz> | 2014-07-31 22:54:08 -0700 | 
| commit | 0f359d5480aa1621d73ee802f420e8951abc620d (patch) | |
| tree | 4bab32c865f9ef7b2bb03247a6be75215cfebf85 | |
| parent | 6c284750de8fe49d7d4cdc6a9a4fb618697780e2 (diff) | |
new 477 report: deployment info, combined browse-edit UI, #24047
25 files changed, 1331 insertions, 154 deletions
diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index 9b9642ed1..8d4d67ba6 100644 --- a/FS/FS/AccessRight.pm +++ b/FS/FS/AccessRight.pm @@ -359,6 +359,9 @@ tie my %rights, 'Tie::IxHash',      'Bulk edit package definitions', +    'Edit FCC report configuration', +    { rightname => 'Edit FCC report configuration for all agents', global=>1 }, +      'Edit CDR rates',      #{ rightname=>'Edit global CDR rates', global=>1, }, diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index 4f43c6baa..93eca5e48 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -387,6 +387,9 @@ if ( -e $addl_handler_use_file ) {    use FS::state;    use FS::state;    use FS::queue_stat; +  use FS::deploy_zone; +  use FS::deploy_zone_block; +  use FS::deploy_zone_vertex;    # Sammath Naur    if ( $FS::Mason::addl_handler_use ) { diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 24f2a25dd..200575612 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -124,6 +124,8 @@ FS::Record - Database record objects      $error = $record->ut_floatn('column');      $error = $record->ut_number('column');      $error = $record->ut_numbern('column'); +    $error = $record->ut_decimal('column'); +    $error = $record->ut_decimaln('column');      $error = $record->ut_snumber('column');      $error = $record->ut_snumbern('column');      $error = $record->ut_money('column'); @@ -2434,6 +2436,35 @@ sub ut_numbern {    '';  } +=item ut_decimal COLUMN[, DIGITS] + +Check/untaint decimal numbers (up to DIGITS decimal places.  If there is an  +error, returns the error, otherwise returns false. + +=item ut_decimaln COLUMN[, DIGITS] + +Check/untaint decimal numbers.  May be null.  If there is an error, returns +the error, otherwise returns false. + +=cut + +sub ut_decimal { +  my($self, $field, $digits) = @_; +  $digits ||= ''; +  $self->getfield($field) =~ /^\s*(\d+(\.\d{0,$digits})?)\s*$/ +    or return "Illegal or empty (decimal) $field: ".$self->getfield($field); +  $self->setfield($field, $1); +  ''; +} + +sub ut_decimaln { +  my($self, $field, $digits) = @_; +  $self->getfield($field) =~ /^\s*(\d*(\.\d{0,$digits})?)\s*$/ +    or return "Illegal (decimal) $field: ".$self->getfield($field); +  $self->setfield($field, $1); +  ''; +} +  =item ut_money COLUMN  Check/untaint monetary numbers.  May be negative.  Set to 0 if null.  If there diff --git a/FS/FS/Report/FCC_477.pm b/FS/FS/Report/FCC_477.pm index 79f00e371..bf4754d0e 100644 --- a/FS/FS/Report/FCC_477.pm +++ b/FS/FS/Report/FCC_477.pm @@ -165,8 +165,6 @@ sub save_fcc477map {    local $FS::UID::AutoCommit = 0;    my $dbh = dbh; -  # lame (should be normal FS::Record access) -    my $sql = "delete from fcc477map where formkey = ?";    my $sth = dbh->prepare($sql) or die dbh->errstr;    $sth->execute($key) or do { @@ -204,6 +202,8 @@ sub statenum2state {    my $num = shift;    $states{$num};  } +### everything above this point is unmaintained ### +  =head1 THE "NEW" REPORT (October 2014 and later) @@ -259,15 +259,14 @@ sub is_fixed_broadband {    ).")";  } -=item part6 OPTIONS +=item report_fixed_broadband OPTIONS -Returns Part 6 of the 2014 FCC 477 data, as an arrayref of arrayrefs. -OPTIONS may contain: +Returns the Fixed Broadband Subscription report (section 5.4), as an arrayref +of an arrayrefs.  OPTIONS may contain:  - date: a timestamp value to count active packages as of that date  - agentnum: limit to customers of that agent -Part 6 is the broadband subscription detail report.  Columns of the  -report are: +Columns of this report are:  - census tract  - technology code  - downstream speed @@ -278,7 +277,7 @@ report are:  =cut -sub part6 { +sub report_fixed_broadband {    my $class = shift;    my %opt = shift;    my $date = $opt{date} || time; @@ -323,10 +322,67 @@ sub part6 {    dbh->selectall_arrayref($statement);  } -=item part9 OPTIONS +=item report_fixed_voice OPTIONS + +Returns the Fixed Voice Subscription Detail report (section 5.5).  OPTIONS +are as above.  Columns are: + +- census tract +- service type (0 for non-VoIP, 1 for VoIP) +(the above columns form a key) +- VGE lines/VoIP subscriptions in service +- consumer grade VGE lines/VoIP subscriptions + +=cut + +sub report_fixed_voice { +  my $class = shift; +  my %opt = shift; +  my $date = $opt{date} || time; +  my $agentnum = $opt{agentnum}; + +  my @select = ( +    'cust_location.censustract', +    # VoIP indicator (0 for non-VoIP, 1 for VoIP) +    'COALESCE(is_voip, 0)', +    # number of lines/subscriptions +    'SUM(CASE WHEN is_voip = 1 THEN 1 ELSE phone_lines END)', +    # consumer grade lines/subscriptions +    'SUM(CASE WHEN is_consumer = 1 THEN ( CASE WHEN is_voip = 1 THEN voip_sessions ELSE phone_lines END) ELSE 0 END)' +  ); + +  my $from = 'cust_pkg +    JOIN cust_location ON (cust_pkg.locationnum = cust_location.locationnum) +    JOIN cust_main ON (cust_pkg.custnum = cust_main.custnum) +    JOIN part_pkg USING (pkgpart) '. +    join_optionnames_int(qw( +      is_phone is_voip is_consumer phone_lines voip_sessions +      )) +  ; + +  my @where = ( +    active_on($date), +    "(is_voip = 1 OR is_phone = 1)", +  ); +  push @where, "cust_main.agentnum = $agentnum" if $agentnum; +  my $group_by = 'cust_location.censustract, COALESCE(is_voip, 0)'; +  my $order_by = $group_by; + +  my $statement = "SELECT ".join(', ', @select) . " +  FROM $from +  WHERE ".join(' AND ', @where)." +  GROUP BY $group_by +  ORDER BY $order_by +  "; + +  warn $statement if $DEBUG; +  dbh->selectall_arrayref($statement); +} + +=item report_local_phone OPTIONS -Returns Part 9 of the 2014 FCC 477 data.  Part 9 is the Local Exchange  -Telephone Subscription report.  Columns are: +Returns the Local Exchange Telephone Subscription report (section 5.6).   +OPTIONS are as above.  Each row is data for one state.  Columns are:  - state FIPS code (key)  - wholesale switched voice lines @@ -346,7 +402,7 @@ Telephone Subscription report.  Columns are:  =cut -sub part9 { +sub report_local_phone {    my $class = shift;    my %opt = shift;    my $date = $opt{date} || time; @@ -358,10 +414,10 @@ sub part9 {      "SUM(phone_circuits)",      "SUM(phone_lines)",      "SUM(CASE WHEN is_broadband = 1 THEN phone_lines ELSE 0 END)", -    "SUM(CASE WHEN is_consumer = 1 AND is_longdistance IS NULL THEN phone_lines ELSE 0 END)", -    "SUM(CASE WHEN is_consumer = 1 AND is_longdistance = 1 THEN phone_lines ELSE 0 END)", -    "SUM(CASE WHEN is_consumer IS NULL AND is_longdistance IS NULL THEN phone_lines ELSE 0 END)", -    "SUM(CASE WHEN is_consumer IS NULL AND is_longdistance = 1 THEN phone_lines ELSE 0 END)", +    "SUM(CASE WHEN is_consumer = 1 AND phone_longdistance IS NULL THEN phone_lines ELSE 0 END)", +    "SUM(CASE WHEN is_consumer = 1 AND phone_longdistance = 1 THEN phone_lines ELSE 0 END)", +    "SUM(CASE WHEN is_consumer IS NULL AND phone_longdistance IS NULL THEN phone_lines ELSE 0 END)", +    "SUM(CASE WHEN is_consumer IS NULL AND phone_longdistance = 1 THEN phone_lines ELSE 0 END)",      "SUM(CASE WHEN phone_localloop = 'owned' THEN phone_lines ELSE 0 END)",      "SUM(CASE WHEN phone_localloop = 'leased' THEN phone_lines ELSE 0 END)",      "SUM(CASE WHEN phone_localloop = 'resale' THEN phone_lines ELSE 0 END)", @@ -378,7 +434,7 @@ sub part9 {        join_optionnames_int(qw(          is_phone is_broadband          phone_vges phone_circuits phone_lines -        is_consumer is_longdistance +        is_consumer phone_longdistance          )).        join_optionnames('media', 'phone_localloop')    ; @@ -401,7 +457,26 @@ sub part9 {    dbh->selectall_arrayref($statement);  } -sub part10 { +=item report_voip OPTIONS + +Returns the Interconnected VoIP Subscription report (section 5.7).   +OPTIONS are as above.  Columns are: + +- state FIPS code (key) +- OTT subscriptions (non-last-mile) +- OTT subscriptions sold to consumers +- last-mile subscriptions +- last-mile subscriptions sold to consumers +- last-mile subscriptions bundled with broadband Internet +- last-mile subscriptions over copper pairs +- last-mile subscriptions over coaxial +- last-mile subscriptions over fiber +- last-mile subscriptions over fixed wireless +- last-mile subscriptions over other media + +=cut + +sub report_voip {    my $class = shift;    my %opt = shift;    my $date = $opt{date} || time; @@ -453,54 +528,5 @@ sub part10 {    dbh->selectall_arrayref($statement);  } -=item part11 OPTIONS - -Returns part 11 (voice subscription detail), as above. - -=cut - -sub part11 { -  my $class = shift; -  my %opt = shift; -  my $date = $opt{date} || time; -  my $agentnum = $opt{agentnum}; - -  my @select = ( -    'cust_location.censustract', -    # VoIP indicator (0 for non-VoIP, 1 for VoIP) -    'COALESCE(is_voip, 0)', -    # number of lines/subscriptions -    'SUM(CASE WHEN is_voip = 1 THEN 1 ELSE phone_lines END)', -    # consumer grade lines/subscriptions -    'SUM(CASE WHEN is_consumer = 1 THEN ( CASE WHEN is_voip = 1 THEN 1 ELSE phone_lines END) ELSE 0 END)' -  ); - -  my $from = 'cust_pkg -    JOIN cust_location ON (cust_pkg.locationnum = cust_location.locationnum) -    JOIN cust_main ON (cust_pkg.custnum = cust_main.custnum) -    JOIN part_pkg USING (pkgpart) '. -    join_optionnames_int(qw( -      is_phone is_voip is_consumer phone_lines -      )) -  ; - -  my @where = ( -    active_on($date), -    "(is_voip = 1 OR is_phone = 1)", -  ); -  push @where, "cust_main.agentnum = $agentnum" if $agentnum; -  my $group_by = 'cust_location.censustract, COALESCE(is_voip, 0)'; -  my $order_by = $group_by; - -  my $statement = "SELECT ".join(', ', @select) . " -  FROM $from -  WHERE ".join(' AND ', @where)." -  GROUP BY $group_by -  ORDER BY $order_by -  "; - -  warn $statement if $DEBUG; -  dbh->selectall_arrayref($statement); -}  1; diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 27bd81374..830b39a84 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -6672,6 +6672,75 @@ sub tables_hashref {        'index' => [],      }, +    # eventually link to tower/sector? +    'deploy_zone' => { +      'columns' => [ +        'zonenum',        'serial',  '',     '',      '', '', +        'description',    'char',    'NULL', $char_d, '', '', +        'agentnum',       'int',     '',     '',      '', '', +        'dbaname',        'char',    'NULL', $char_d, '', '', +        'zonetype',       'char',    '',     1,       '', '', +        'technology',     'int',     '',     '',      '', '', +        'spectrum',       'int',     'NULL', '',      '', '', +        'servicetype',    'char',    '',     '12',    '', '', +        'adv_speed_up',   'decimal', 'NULL', '10,3',  '', '', +        'adv_speed_down', 'decimal', 'NULL', '10,3',  '', '', +        'cir_speed_up',   'decimal', 'NULL', '10,3',  '', '', +        'cir_speed_down', 'decimal', 'NULL', '10,3',  '', '', +        'is_consumer',    'char',    'NULL', 1,       '', '', +        'is_business',    'char',    'NULL', 1,       '', '', +      ], +      'primary_key' => 'zonenum', +      'unique' => [], +      'index'  => [ [ 'agentnum' ] ], +      'foreign_keys' => [ +                          { columns     => [ 'agentnum' ], +                            table       => 'agent', +                            references  => [ 'agentnum' ], +                          }, +                        ], +    }, + +    'deploy_zone_block' => { +      'columns' => [ +        'blocknum',       'serial',  '',     '',      '', '', +        'zonenum',        'int',     '',     '',      '', '', +        'censusblock',    'char',    '',     15,      '', '', +        'censusyear',     'char',    '',      4,      '', '', +      ], +      'primary_key' => 'blocknum', +      'unique' => [], +      'index'  => [ [ 'zonenum' ] ], +      'foreign_keys' => [ +                          { columns     => [ 'zonenum' ], +                            table       => 'deploy_zone', +                            references  => [ 'zonenum' ], +                          }, +                        ], +    }, + +    'deploy_zone_vertex' => { +      'columns' => [ +        'vertexnum',      'serial',  '',     '',      '', '', +        'zonenum',        'int',     '',     '',      '', '', +        'latitude',       'decimal', '',     '10,7',  '', '',  +        'longitude',      'decimal', '',     '10,7',  '', '',  +        'sequence',       'int',     '',     '',      '', '', +      ], +      'primary_key' => 'vertexnum', +      'unique' => [ [ 'zonenum', 'sequence' ] ], +      'index'  => [ ], +      'foreign_keys' => [ +                          { columns     => [ 'zonenum' ], +                            table       => 'deploy_zone', +                            references  => [ 'zonenum' ], +                          }, +                        ], +    }, + + + +      # name type nullability length default local diff --git a/FS/FS/deploy_zone.pm b/FS/FS/deploy_zone.pm new file mode 100644 index 000000000..3caeda24b --- /dev/null +++ b/FS/FS/deploy_zone.pm @@ -0,0 +1,221 @@ +package FS::deploy_zone; + +use strict; +use base qw( FS::o2m_Common FS::Record ); +use FS::Record qw( qsearch qsearchs dbh ); + +=head1 NAME + +FS::deploy_zone - Object methods for deploy_zone records + +=head1 SYNOPSIS + +  use FS::deploy_zone; + +  $record = new FS::deploy_zone \%hash; +  $record = new FS::deploy_zone { 'column' => 'value' }; + +  $error = $record->insert; + +  $error = $new_record->replace($old_record); + +  $error = $record->delete; + +  $error = $record->check; + +=head1 DESCRIPTION + +An FS::deploy_zone object represents a geographic zone where a certain kind +of service is available.  Currently we store this information to generate +the FCC Form 477 deployment reports, but it may find other uses later. + +FS::deploy_zone inherits from FS::Record.  The following fields are currently +supported: + +=over 4 + +=item zonenum + +primary key + +=item description + +Optional text describing the zone. + +=item agentnum + +The agent that serves this zone. + +=item dbaname + +The name under which service is marketed in this zone.  If null, will  +default to the agent name. + +=item zonetype + +The way the zone geography is defined: "B" for a list of census blocks +(used by the FCC for fixed broadband service), "P" for a polygon (for  +mobile services).  See L<FS::deploy_zone_block> and L<FS::deploy_zone_vertex>. + +=item technology + +The FCC technology code for the type of service available. + +=item spectrum + +For mobile service zones, the FCC code for the RF band. + +=item servicetype + +"broadband" or "voice" + +=item adv_speed_up + +For broadband, the advertised upstream bandwidth in the zone.  If multiple +speed tiers are advertised, use the highest. + +=item adv_speed_down + +For broadband, the advertised downstream bandwidth in the zone. + +=item cir_speed_up + +For broadband, the contractually guaranteed upstream bandwidth, if that type +of service is sold. + +=item cir_speed_down + +For broadband, the contractually guaranteed downstream bandwidth, if that  +type of service is sold. + +=item is_consumer + +'Y' if this service is sold for consumer/household use. + +=item is_business + +'Y' if this service is sold to business or institutional use.  Not mutually +exclusive with is_consumer. + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new zone.  To add the zone to the database, see L<"insert">. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'deploy_zone'; } + +=item insert ELEMENTS + +Adds this record to the database.  If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +sub delete { +  my $oldAutoCommit = $FS::UID::AutoCommit; +  local $FS::UID::AutoCommit = 0; +  # clean up linked records +  my $self = shift; +  my $error = $self->process_o2m( +    'table'   => $self->element_table, +    'num_col' => 'zonenum', +    'fields'  => 'zonenum', +    'params'  => {}, +  ) || $self->SUPER::delete(@_); +   +  if ($error) { +    dbh->rollback if $oldAutoCommit; +    return $error; +  } +  ''; +} + +=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 + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid zone record.  If there is +an error, returns the error, otherwise returns false.  Called by the insert +and replace methods. + +=cut + +sub check { +  my $self = shift; + +  my $error =  +    $self->ut_numbern('zonenum') +    || $self->ut_textn('description') +    || $self->ut_number('agentnum') +    || $self->ut_foreign_key('agentnum', 'agent', 'agentnum') +    || $self->ut_alphan('dbaname') +    || $self->ut_enum('zonetype', [ 'B', 'P' ]) +    || $self->ut_number('technology') +    || $self->ut_numbern('spectrum') +    || $self->ut_enum('servicetype', [ 'broadband', 'voice' ]) +    || $self->ut_decimaln('adv_speed_up', 3) +    || $self->ut_decimaln('adv_speed_down', 3) +    || $self->ut_decimaln('cir_speed_up', 3) +    || $self->ut_decimaln('cir_speed_down', 3) +    || $self->ut_flag('is_consumer') +    || $self->ut_flag('is_business') +  ; +  return $error if $error; + +  $self->SUPER::check; +} + +=item element_table + +Returns the name of the table that contains the zone's elements (blocks or +vertices). + +=cut + +sub element_table { +  my $self = shift; +  if ($self->zonetype eq 'B') { +    return 'deploy_zone_block'; +  } elsif ( $self->zonetype eq 'P') { +    return 'deploy_zone_vertex'; +  } else { +    die 'unknown zonetype'; +  } +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::Record> + +=cut + +1; + diff --git a/FS/FS/deploy_zone_block.pm b/FS/FS/deploy_zone_block.pm new file mode 100644 index 000000000..58234b924 --- /dev/null +++ b/FS/FS/deploy_zone_block.pm @@ -0,0 +1,126 @@ +package FS::deploy_zone_block; + +use strict; +use base qw( FS::Record ); +use FS::Record qw( qsearch qsearchs ); + +=head1 NAME + +FS::deploy_zone_block - Object methods for deploy_zone_block records + +=head1 SYNOPSIS + +  use FS::deploy_zone_block; + +  $record = new FS::deploy_zone_block \%hash; +  $record = new FS::deploy_zone_block { 'column' => 'value' }; + +  $error = $record->insert; + +  $error = $new_record->replace($old_record); + +  $error = $record->delete; + +  $error = $record->check; + +=head1 DESCRIPTION + +An FS::deploy_zone_block object represents a census block that's part of +a deployment zone.  FS::deploy_zone_block inherits from FS::Record.  The  +following fields are currently supported: + +=over 4 + +=item blocknum + +primary key + +=item zonenum + +L<FS::deploy_zone> foreign key for the zone. + +=item censusblock + +U.S. census block number (15 digits). + +=item censusyear + +The year of the census map where the block appeared or was last verified. + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new block entry.  To add the recordto 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 + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'deploy_zone_block'; } + +=item insert + +Adds this record to the database.  If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=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 + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid record.  If there is +an error, returns the error, otherwise returns false.  Called by the insert +and replace methods. + +=cut + +sub check { +  my $self = shift; + +  my $error =  +    $self->ut_numbern('blocknum') +    || $self->ut_number('zonenum') +    || $self->ut_number('censusblock') +    || $self->ut_number('censusyear') +  ; +  return $error if $error; + +  $self->SUPER::check; +} + +=back + +=head1 SEE ALSO + +L<FS::Record> + +=cut + +1; + diff --git a/FS/FS/deploy_zone_vertex.pm b/FS/FS/deploy_zone_vertex.pm new file mode 100644 index 000000000..a25bfde23 --- /dev/null +++ b/FS/FS/deploy_zone_vertex.pm @@ -0,0 +1,125 @@ +package FS::deploy_zone_vertex; + +use strict; +use base qw( FS::Record ); +use FS::Record qw( qsearch qsearchs ); + +=head1 NAME + +FS::deploy_zone_vertex - Object methods for deploy_zone_vertex records + +=head1 SYNOPSIS + +  use FS::deploy_zone_vertex; + +  $record = new FS::deploy_zone_vertex \%hash; +  $record = new FS::deploy_zone_vertex { 'column' => 'value' }; + +  $error = $record->insert; + +  $error = $new_record->replace($old_record); + +  $error = $record->delete; + +  $error = $record->check; + +=head1 DESCRIPTION + +An FS::deploy_zone_vertex object represents a vertex of a polygonal  +deployment zone (L<FS::deploy_zone>).  FS::deploy_zone_vertex inherits from +FS::Record.  The following fields are currently supported: + +=over 4 + +=item vertexnum + +primary key + +=item zonenum + +Foreign key to L<FS::deploy_zone>. + +=item latitude + +Latitude, as a decimal; positive values are north of the Equator. + +=item longitude + +Longitude, as a decimal; positive values are east of Greenwich. + +=item sequence + +The ordinal position of this vertex, starting with zero. + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new vertex record.  To add the record to the database, see L<"insert">. + +=cut + +sub table { 'deploy_zone_vertex'; } + +=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 + +=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 vertex.  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('vertexnum') +    || $self->ut_number('zonenum') +    || $self->ut_coord('latitude') +    || $self->ut_coord('longitude') +    || $self->ut_number('sequence') +  ; +  return $error if $error; + +  $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::Record> + +=cut + +1; + diff --git a/FS/FS/part_pkg_fcc_option.pm b/FS/FS/part_pkg_fcc_option.pm index 0a288def5..a090b96ae 100644 --- a/FS/FS/part_pkg_fcc_option.pm +++ b/FS/FS/part_pkg_fcc_option.pm @@ -112,31 +112,31 @@ tie our %media_types, 'Tie::IxHash', (    'Other'           => [ 90, 0 ],  ); -our %technology_labels = ( -      10 => 'Other ADSL', -      11 => 'ADSL2', -      12 => 'VDSL', -      20 => 'SDSL', -      30 => 'Other Copper Wireline', -      40 => 'Other Cable Modem', -      41 => 'Cable - DOCSIS 1, 1.1, 2.0', -      42 => 'Cable - DOCSIS 3.0', -      50 => 'Fiber', -      60 => 'Satellite', -      70 => 'Terrestrial Fixed Wireless', -      # mobile wireless -      80 => 'Mobile - WCDMA/UMTS/HSPA', -      81 => 'Mobile - HSPA+', -      82 => 'Mobile - EVDO/EVDO Rev A', -      83 => 'Mobile - LTE', -      84 => 'Mobile - WiMAX', -      85 => 'Mobile - CDMA', -      86 => 'Mobile - GSM', -      87 => 'Mobile - Analog', -      88 => 'Other Mobile', - -      90 => 'Electric Power Line', -      0  => 'Other' +tie our %technology_labels, 'Tie::IxHash',  ( +  10 => 'Other ADSL', +  11 => 'ADSL2', +  12 => 'VDSL', +  20 => 'SDSL', +  30 => 'Other Copper Wireline', +  40 => 'Other Cable Modem', +  41 => 'Cable - DOCSIS 1, 1.1, 2.0', +  42 => 'Cable - DOCSIS 3.0', +  50 => 'Fiber', +  60 => 'Satellite', +  70 => 'Terrestrial Fixed Wireless', +  # mobile wireless +  80 => 'Mobile - WCDMA/UMTS/HSPA', +  81 => 'Mobile - HSPA+', +  82 => 'Mobile - EVDO/EVDO Rev A', +  83 => 'Mobile - LTE', +  84 => 'Mobile - WiMAX', +  85 => 'Mobile - CDMA', +  86 => 'Mobile - GSM', +  87 => 'Mobile - Analog', +  88 => 'Other Mobile', + +  90 => 'Electric Power Line', +  0  => 'Other'  );  sub media_types { @@ -144,7 +144,7 @@ sub media_types {  }  sub technology_labels { -  +{ %technology_labels }; +  Storable::dclone(\%technology_labels);  }  =head1 BUGS diff --git a/FS/MANIFEST b/FS/MANIFEST index 693904d71..9a9573dbd 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -812,3 +812,9 @@ FS/state.pm  t/state.t  FS/queue_stat.pm  t/queue_stat.t +FS/deploy_zone.pm +t/deploy_zone.t +FS/deploy_zone_block.pm +t/deploy_zone_block.t +FS/deploy_zone_vertex.pm +t/deploy_zone_vertex.t diff --git a/FS/t/deploy_zone.t b/FS/t/deploy_zone.t new file mode 100644 index 000000000..d220e81c7 --- /dev/null +++ b/FS/t/deploy_zone.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::deploy_zone; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/deploy_zone_block.t b/FS/t/deploy_zone_block.t new file mode 100644 index 000000000..c3241b158 --- /dev/null +++ b/FS/t/deploy_zone_block.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::deploy_zone_block; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/deploy_zone_vertex.t b/FS/t/deploy_zone_vertex.t new file mode 100644 index 000000000..78c079ffd --- /dev/null +++ b/FS/t/deploy_zone_vertex.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::deploy_zone_vertex; +$loaded=1; +print "ok 1\n"; diff --git a/bin/convert-477-options b/bin/convert-477-options index 2b8970aae..99a6ea5e0 100755 --- a/bin/convert-477-options +++ b/bin/convert-477-options @@ -121,7 +121,7 @@ for my $part_pkg (qsearch('part_pkg', { freq => {op => '!=', value => '0'}})) {      if ($part_pkg->fcc_voip_class) {        # there's no such thing as a VoIP DS0 equivalent, but this is        # what we used the field for -      push @fcc_opts, 'voip_lines' => $part_pkg->fcc_ds0s; +      push @fcc_opts, 'voip_sessions' => $part_pkg->fcc_ds0s;      } else {        push @fcc_opts, 'phone_lines' => $part_pkg->fcc_ds0s, 'is_phone' => 1;      } diff --git a/httemplate/browse/deploy_zone.html b/httemplate/browse/deploy_zone.html new file mode 100644 index 000000000..489a22658 --- /dev/null +++ b/httemplate/browse/deploy_zone.html @@ -0,0 +1,72 @@ +<& /elements/header.html, 'Deployment zones' &> +<& /elements/menubar.html, +  'Add a new fixed broadband zone' => $p.'edit/deploy_zone-fixed.html', +  'Add a new mobile zone'          => $p.'edit/deploy_zone-mobile.html', +&> +<P><FONT SIZE="+1"><B>Fixed Broadband Zones</B></FONT></P> +<& elements/browse.html, +  name_singular   => 'zone', +  query           => { table      => 'deploy_zone', +                       hashref    => { zonetype => 'B' }, +                     }, +  count_query     => "SELECT COUNT(*) FROM deploy_zone WHERE zonetype = 'B'", +  agent_virt      => 1, +  header          => [  '#', +                        'Description', +                        'Technology', +                        'Market', +                        'Advertised Mbps', +                        'Contractual Mbps', +                        'Census blocks', +                     ], +  fields          => [  'zonenum', +                        'description', +                        sub { my $self = shift; +                              $tech_label->{$self->technology} }, +                        sub { my $self = shift; +                              join( ' / ', +                                $self->is_consumer ? 'consumer' : (), +                                $self->is_business ? 'business' : () +                              ) +                            }, +                        sub { my $self = shift; +                              join( ' / ', grep $_, +                                $self->adv_speed_down, +                                $self->adv_speed_up +                              ) +                            }, +                        sub { my $self = shift; +                              join( ' / ', grep $_, +                                $self->cir_speed_down, +                                $self->cir_speed_up +                              ) +                            }, +                        sub { my $self = shift; +                              FS::deploy_zone_block->count('zonenum = '.$self->zonenum) +                            }, +                     ], +  sort_fields     => [ 'zonenum', +                       'description', +                       'technology', +                       'is_consumer is not null, is_business is not null', +                       'adv_speed_down, adv_speed_up', +                       'cir_speed_down, cir_speed_up', +                     ], +  links           => [  '', $link_fixed, ], +  align           => 'clllllr', +  nohtmlheader    => 1, +  disable_maxselect => 1, +  disable_total     => 1, +&> +<%init> +my $curuser = $FS::CurrentUser::CurrentUser; +my $acl_edit = $curuser->access_right('Edit FCC report configuration'); +my $acl_edit_global = $curuser->access_right('Edit FCC report configuration for all agents'); +die "access denied" +  unless $acl_edit or $acl_edit_global; + +my $link_fixed = [ $p.'edit/deploy_zone-fixed.html?', 'zonenum' ]; +my $link_mobile= [ $p.'edit/deploy_zone-mobile.html', 'zonenum' ]; + +my $tech_label = FS::part_pkg_fcc_option->technology_labels; +</%init> diff --git a/httemplate/browse/part_pkg-fcc.html b/httemplate/browse/part_pkg-fcc.html new file mode 100755 index 000000000..9462c3248 --- /dev/null +++ b/httemplate/browse/part_pkg-fcc.html @@ -0,0 +1,215 @@ +<& elements/browse.html, +  'title'                 => 'Package Definitions - FCC Options', +  'menubar'               => \@menubar, +  'html_init'             => $html_init, +  'html_form'             => $html_form, +  'html_posttotal'        => $html_posttotal, +  'name'                  => 'package definitions', +  'disableable'           => 1, +  'disabled_statuspos'    => 4, +  'agent_virt'            => 1, +  'agent_null_right'      => [ $edit, $edit_global ], +  'agent_null_right_link' => $edit_global, +  'agent_pos'             => 6, +  'query'                 => +                            { 'select'    => $select, +                              'table'     => 'part_pkg', +                              'addl_from' => $addl_from, +                              'hashref'   => \%hash, +                              'extra_sql' => $extra_sql, +                              'order_by'  => "ORDER BY $orderby" +                            }, +  'count_query'           => $count_query, +  'header'                => \@header, +  'fields'                => \@fields, +  'links'                 => \@links, +  'align'                 => $align, +  'link_field'            => 'pkgpart', +  'html_foot'             => $html_foot, +&> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +my $edit        = 'Edit package definitions'; +my $edit_global = 'Edit global package definitions'; +my $acl_edit        = $curuser->access_right($edit); +my $acl_edit_global = $curuser->access_right($edit_global); + +die "access denied" +  unless $acl_edit || $acl_edit_global; + +my $conf = new FS::Conf; + +my $orderby = 'pkgpart'; +my %hash = (); +my $extra_count = ''; + +my @where = (); + +# only ever show recurring packages here +$hash{'freq'} = { op=>'!=', value=>'0' }; +$extra_count = " freq != '0' "; + +# filter by classnum +my $classnum = ''; +if ( $cgi->param('classnum') =~ /^(\d+)$/ ) { +  $classnum = $1; +  push @where, $classnum ? "classnum =  $classnum" +                         : "classnum IS NULL"; +} +$cgi->delete('classnum'); + +# filter by agent permissions +push @where, FS::part_pkg->curuser_pkgs_sql +  unless $acl_edit_global; + +my $extra_sql = scalar(@where) +                ? ( scalar(keys %hash) ? ' AND ' : ' WHERE ' ). +                  join( 'AND ', @where) +                : ''; + +# pull option values into the select +my @optionnames = ( qw( +  media +  is_consumer +  is_broadband technology broadband_upstream broadband_downstream +  is_phone phone_wholesale phone_vges phone_circuits  +  phone_lines phone_longdistance phone_localloop +  is_voip voip_lastmile voip_sessions +) ); + +my $select = join(',', +  'part_pkg.*', +  '(SELECT classname FROM pkg_class WHERE pkg_class.classnum = part_pkg.classnum) AS classname', # grr, disableable... +  @optionnames +); + +my $addl_from =  +  FS::Report::FCC_477::join_optionnames(@optionnames); + +#restore this so pagination works +$cgi->param('classnum', $classnum) if length($classnum); + +#should hide this if there aren't any classes +my $html_posttotal = +  "<BR>( show class: ". +  include('/elements/select-pkg_class.html', +            #'curr_value'    => $classnum, +            'value'         => $classnum, #insist on 0 :/ +            'onchange'      => 'filter_change()', +            'pre_options'   => [ '-1' => 'all', +                                 '0'  => '(none)', ], +            'disable_empty' => 1, +         ). +  ' )'; + +my $link = [ $p.'edit/part_pkg.cgi?', 'pkgpart' ]; + +my @header = ( '#', 'Package', 'Comment' ); +my @fields = ( 'pkgpart', 'pkg', 'comment' ,); +my $align = 'rll'; +my @links = ( $link, $link, '', ); + +unless ( length($classnum) ) { +  push @header, 'Class'; +  push @fields, 'classname'; +  $align .= 'l'; +} + +# still include the report_option classes, to help with migration +# but not other plan options + +my %report_optionname_name = map { 'report_option_'.$_->num, $_->name } +  qsearch('part_pkg_report_option', { disabled => '' }); + +push @header, 'Report classes'; + +push @fields,  +              sub { +                    my $part_pkg = shift; +                    my %options = $part_pkg->options; +                    # gather any options that are really report options, +                    # convert them to their user-friendly names, +                    # and sort them (I think?) +                    my @report_options = +                      sort { $a cmp $b } +                      map { $report_optionname_name{$_} } +                      grep { $options{$_} +                             and exists($report_optionname_name{$_}) } +                      keys %options; + +                    my @rows; +                    foreach (@report_options) { +                      push @rows, [ +                        { 'data'  => $_, +                          'align' => 'center', +                          'colspan' => 2 +                        } +                      ]; +                    } # foreach @report_options +                    \@rows; +                  }; + +$align .= 'cr'; + +# -------- +# now the FCC option part +# -------- + +my @pkgparts; +push @header, 'FCC report parameters'; +push @fields, sub { +  my $part_pkg = shift; +  my %hash = $part_pkg->fcc_options; +  include('/elements/input-fcc_options.html', +            id          => 'pkgpart'.$part_pkg->pkgpart, +            curr_value  => encode_json(\%hash), +            html_only   => 1 +  ); +}; +$align .= 'l'; + +my $count_extra_sql = $extra_sql; +$count_extra_sql =~ s/^\s*AND /WHERE /i; +$extra_count = ( $count_extra_sql ? ' AND ' : ' WHERE ' ). $extra_count +  if $extra_count; +my $count_query = "SELECT COUNT(*) FROM part_pkg $count_extra_sql $extra_count"; + +my $html_init =  +  include('/elements/init_overlib.html') . +  include('/elements/input-fcc_options.html', js_only => 1) . +  include('.style'); + +my $html_form = ''; +my $html_foot = ''; +# insert a checkbox column +unshift @header, ''; +unshift @fields, sub { +  '<INPUT TYPE="checkbox" NAME="pkgpart" VALUE=' . $_[0]->pkgpart .'>'; +}; +unshift @links, ''; +$align = 'c'.$align; + + +$html_form = qq!<FORM ACTION="${p}edit/process/bulk-part_pkg-fcc.html" METHOD="POST">!; +$html_foot = qq! +  <INPUT TYPE="submit" VALUE="Save changes"> +  </FORM>!; + +my @menubar = +  ( 'Package definitions' => $p.'browse/part_pkg.cgi' ); + +</%init> +<%def .style> +<style> +  ul.fcc_options { +    text-align: left; +  } +  ul.fcc_options li { +  } +  button.edit_fcc_options { +    float: right; +  } +</style> +</%def> diff --git a/httemplate/edit/deploy_zone-fixed.html b/httemplate/edit/deploy_zone-fixed.html new file mode 100644 index 000000000..ecec9c434 --- /dev/null +++ b/httemplate/edit/deploy_zone-fixed.html @@ -0,0 +1,87 @@ +<& elements/edit.html, +    'name_singular' => 'deployment zone', +    'table'         => 'deploy_zone', +    'post_url'      => popurl(1).'process/deploy_zone-fixed.html', +    'labels'        => { +        'description'     => 'Description', +        'agentnum'        => 'Agent', +        'dbaname'         => 'Business name (if different from agent)', +        'technology'      => 'Technology', +        'adv_speed_up'    => 'Upstream', +        'adv_speed_down'  => 'Downstream', +        'cir_speed_up'    => 'Upstream', +        'cir_speed_down'  => 'Downstream', +        'is_consumer'     => 'Consumer/mass market', +        'is_business'     => 'Business/government', +        'blocknum'        => '', +    }, +    'fields'        => [ +        { field         => 'zonetype', +          type          => 'hidden', +          value         => 'B' +        }, +        { field         => 'servicetype', +          type          => 'hidden', +          value         => 'broadband' +        }, +        'description', +        { field         => 'agentnum', +          type          => 'select-agent', +          disable_empty => 1, +          viewall_right => 'Edit FCC report configuration for all agents', +        }, +        'dbaname', +        { field         => 'technology', +          type          => 'select', +          options       => [ keys(%$technology_labels) ], +          labels        => $technology_labels, +        }, +        { field         => 'is_consumer', type => 'checkbox', value=>'Y' }, +        { field         => 'is_business', type => 'checkbox', value=>'Y' }, +        { type => 'tablebreak-tr-title', +          value => 'Advertised maximum speed (Mbps)' }, +        'adv_speed_down', +        'adv_speed_up', +        { type => 'tablebreak-tr-title', +          value => 'Contractually guaranteed speed (Mbps)' }, +        'cir_speed_down', +        'cir_speed_up', + +        { type => 'tablebreak-tr-title', value => 'Census blocks'}, +        { field => 'blocknum', +          type              => 'deploy_zone_block', +          o2m_table         => 'deploy_zone_block', +          m2_label          => ' ', +          m2_error_callback => $m2_error_callback, +        }, +    ], + +&> +<%init> +my $curuser = $FS::CurrentUser::CurrentUser; +die "access denied" +  unless $curuser->access_right([ +    'Edit FCC report configuration', +    'Edit FCC report configuration for all agents', +  ]); + +my $technology_labels = FS::part_pkg_fcc_option->technology_labels; + +my $m2_error_callback = sub { +  my ($cgi, $deploy_zone) = @_; +  my @blocknums = grep { +    /^blocknum\d+/ and length($cgi->param($_.'_censusblock')) +  } $cgi->param; + +  map { +    my $k = $_; +    FS::deploy_zone_block->new({ +      blocknum    => scalar($cgi->param($k)), +      zonenum     => $deploy_zone->zonenum, +      censusblock => scalar($cgi->param($k.'_censusblock')), +      censusyear  => scalar($cgi->param($k.'_censusyear')), +    }) +  } @blocknums; +}; + +</%init> diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index 9e55d9f42..65eca6cf4 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -232,23 +232,26 @@                         },                       }, -                     { type  => 'tablebreak-tr-title', -                       value => 'FCC Form 477 information', -                     }, -                     { field => 'fcc_options_string', -                       type  => 'input-fcc_options', -                       curr_value_callback => sub { -                         my ($cgi, $part_pkg, $fref) = @_; -                         if ( $cgi->param('fcc_options_string') ) { -                           # error redirect -                           return $cgi->param('fcc_options_string'); -                         } -                         my %hash; -                         %hash = $part_pkg->fcc_options  -                           if ($part_pkg->pkgpart); -                         return encode_json(\%hash); +                     ($fcc_opts ? ( +                       { type  => 'tablebreak-tr-title', +                         value => 'FCC Form 477 information',                         }, -                     }, +                       { field => 'fcc_options_string', +                         type  => 'input-fcc_options', +                         curr_value_callback => sub { +                           my ($cgi, $part_pkg, $fref) = @_; +                           if ( $cgi->param('fcc_options_string') ) { +                             # error redirect +                             return $cgi->param('fcc_options_string'); +                           } +                           my %hash; +                           %hash = $part_pkg->fcc_options  +                             if ($part_pkg->pkgpart); +                           return encode_json(\%hash); +                         }, +                       }, +                       ) : () +                     ),                       { type  => 'tablebreak-tr-title',                         value => 'External Links', #better name? @@ -405,6 +408,8 @@ my $agent_clone_extra_sql =  my $conf = new FS::Conf;  my $taxproducts = $conf->exists('enable_taxproducts'); +my $fcc_opts = $conf->exists('part_pkg-show_fcc_options'); +  my @locales = grep { ! /^en_/i } $conf->config('available-locales'); #should filter from the default locale lang instead of en_  my %locale_labels =  map {    ( $_ => 'Package -- '. FS::Locales->description($_) ) diff --git a/httemplate/edit/process/deploy_zone-fixed.html b/httemplate/edit/process/deploy_zone-fixed.html new file mode 100644 index 000000000..c14c81c58 --- /dev/null +++ b/httemplate/edit/process/deploy_zone-fixed.html @@ -0,0 +1,9 @@ +<& elements/process.html,  +    error_redirect => popurl(2).'deploy_zone-fixed.html?', +    table       => 'deploy_zone', +    viewall_dir => 'browse', +    process_o2m =>  +      { 'table'  => 'deploy_zone_block', +                     'fields' => [qw( censusblock censusyear )] +      }, +&> diff --git a/httemplate/elements/deploy_zone_block.html b/httemplate/elements/deploy_zone_block.html new file mode 100644 index 000000000..9985944bd --- /dev/null +++ b/httemplate/elements/deploy_zone_block.html @@ -0,0 +1,47 @@ +% unless ( $opt{'js_only'} ) { + +  <INPUT TYPE="hidden" NAME="<%$name%>" ID="<%$id%>" VALUE="<% $curr_value %>"> +  Block  +  <INPUT TYPE  = "text" +         NAME  = "<%$name%>_censusblock" +         ID    = "<%$id%>_censusblock" +         VALUE = "<% scalar($cgi->param($name.'_censusblock')) +                      || $deploy_zone_block->censusblock +                 %>" +         SIZE  = 17 +         MAXLENGTH = 15 +         <% $onchange %> +  > +    +  Year +  <INPUT TYPE  = "text" +         NAME  = "<%$name%>_censusyear"  +         ID    = "<%$id%>_censusyear" +         VALUE = "<% scalar($cgi->param($name.'_censusyear')) +                      || $deploy_zone_block->censusyear +                  %>" +         SIZE  = 5 +         MAXLENGTH = 4 +         <% $onchange %> +  > +% } +<%init> + +my( %opt ) = @_; + +my $name = $opt{'element_name'} || $opt{'field'} || 'blocknum'; +my $id = $opt{'id'} || 'blocknum'; + +my $curr_value = $opt{'curr_value'} || $opt{'value'}; + +my $onchange = $opt{'onchange'}; +if ( $onchange ) { +  $onchange =~ s/\(what\);/(this);/; +  $onchange = 'onchange="'.$onchange.'"'; +} + +my $deploy_zone_block = $curr_value +  ? FS::deploy_zone_block->by_key($curr_value) +  : FS::deploy_zone_block->new; + +</%init> diff --git a/httemplate/elements/input-fcc_options.html b/httemplate/elements/input-fcc_options.html new file mode 100644 index 000000000..b191e1c07 --- /dev/null +++ b/httemplate/elements/input-fcc_options.html @@ -0,0 +1,108 @@ +% unless ($opt{js_only}) { +<& hidden.html, 'field' => $id, @_ &> +%#    <& input-text.html, 'id' => $id, @_ &> # XXX debugging +<UL ID="<%$id%>_display_fcc_options" CLASS="fcc_options"> +</UL> +<button type="button" class="edit_fcc_button" data-target="<% $id %>"> +  Edit +</button> +% } +% unless ($opt{html_only}) { +%   my $popup = $fsurl.'misc/part_pkg_fcc_options.html?id='; +%   my $popup_name = 'popup-'.time. "-$$-". rand() * 2**32; +<SCRIPT TYPE="text/javascript"> +function edit_fcc_options() { +  var id = this.dataset['target']; +  overlib( +    OLiframeContent( '<% $popup %>' + id, +      760, 600, '<% $popup_name %>', 0, 'auto' ), +    CAPTION, 'FCC Form 477 options',  +    STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, +    DRAGGABLE, CLOSECLICK, +    BGCOLOR, '#333399', CGCOLOR, '#333399', +    CLOSETEXT, 'Close' +  ); +} + +var technology_labels = <% encode_json(FS::part_pkg_fcc_option->technology_labels) %>; +function show_fcc_options(id) { +  var curr_values = JSON.parse(document.getElementById(id).value); +  // hardcoded for the same reasons as misc/part_pkg_fcc_options +  var out = ''; +  var tech = curr_values['technology']; +  if ( tech ) { +    if (technology_labels[tech]) { +      tech = technology_labels[tech]; +    } else { +      tech = 'Technology '+tech; // unknown? +    } +  } +  var media = String.toLowerCase(curr_values['media'] || 'unknown media'); +  if ( curr_values['is_consumer'] ) { +    out += '<li><strong>Consumer-grade</strong> service</li>>'; +  } else { +    out += '<li><strong>Business-grade</strong> service</li>'; +  } +  if ( curr_values['is_broadband'] ) { +    out += '<li>Broadband via <strong>' + tech + '</strong>' +        +  '<li><strong>' + curr_values['broadband_downstream'] +        +  'Mbps </strong> down / ' +        +  '<strong>' + curr_values['broadband_upstream'] +        +  'Mbps </strong> up</li>'; +  } +  if ( curr_values['is_phone'] ) { +    if ( curr_values['phone_wholesale'] ) { +      out += '<li>Wholesale telephone</li>'; +      if ( curr_values['phone_vges'] ) { +        out += '<li><strong>' + curr_values['phone_vges'] + '</strong>' +            +  ' switched voice-grade lines</li>'; +      } +      if ( curr_values['phone_circuits'] ) { +        out += '<li><strong>' + curr_values['phone_circuits'] + '</strong>' +            +  ' unswitched circuits</li>'; +      } +    } else { +      // enduser service +      out += '<li>Local telephone over <strong>' + media + '</strong></li>' +          +  '<li><strong>' + curr_values['phone_lines'] +          +  '</strong> voice-grade lines</li>'; +      if ( curr_values['phone_localloop'] == 'resale' ) { +        out += '<li><strong>Resold</strong> from another carrier</li>>'; +      } else if ( curr_values['phone_localloop'] == 'leased' ) { +        out += '<li>Using <strong>leased circuits</strong> from another carrier</li>'; +      } else if ( curr_values['phone_localloop'] == 'owned' ) { +        out += '<li>Using <strong>our own circuits</strong></li>'; +      } +      if ( curr_values['phone_longdistance'] ) { +        out += '<li>Includes <strong>long-distance service</strong></li>'; +      } +    } +  } // is_phone +  if ( curr_values['is_voip'] ) { +    out += '<li><strong>VoIP</strong> telephone service</li>'; +    out += '<li><strong>' + curr_values['voip_sessions'] +  +           '</strong> sessions allowed</li>'; +    if ( curr_values['voip_lastmile'] ) { +      out += '<li><strong>Including</strong> last-mile connection</li>'; +    } else { +      out += '<li>Using a <strong>separate</strong> last-mile connection</li>'; +    } +  } // is_voip + +  var out_ul = document.getElementById(id + '_display_fcc_options'); +  out_ul.innerHTML = out; +} +<&| onload.js &> +  var edit_fcc_buttons = document.getElementsByClassName('edit_fcc_button'); +  for(var i = 0; i < edit_fcc_buttons.length; i++) { +    var button = edit_fcc_buttons[i]; +    show_fcc_options( button.dataset['target'] ); +    button.addEventListener('click', edit_fcc_options); +  } +</&> +</SCRIPT> +% } +<%init> +my %opt = @_; +my $id = $opt{id} || $opt{field}; +</%init> diff --git a/httemplate/elements/tr-input-fcc_options.html b/httemplate/elements/tr-input-fcc_options.html index 11cb4a962..58f7247c4 100644 --- a/httemplate/elements/tr-input-fcc_options.html +++ b/httemplate/elements/tr-input-fcc_options.html @@ -41,9 +41,9 @@ function show_fcc_options() {    }    var media = String.toLowerCase(curr_values['media'] || 'unknown media');    if ( curr_values['is_consumer'] ) { -    out += '<li><strong>Consumer-grade</strong> service</li>>'; +    out += '<li><strong>Consumer-grade</strong></li>>';    } else { -    out += '<li><strong>Business-grade</strong> service</li>'; +    out += '<li><strong>Business-grade</strong></li>';    }    if ( curr_values['is_broadband'] ) {      out += '<li>Broadband via <strong>' + tech + '</strong>' diff --git a/httemplate/misc/part_pkg_fcc_options.html b/httemplate/misc/part_pkg_fcc_options.html index f74328446..a5ecb12bc 100644 --- a/httemplate/misc/part_pkg_fcc_options.html +++ b/httemplate/misc/part_pkg_fcc_options.html @@ -90,6 +90,8 @@      <& .checkbox, 'is_voip' &>      <LABEL FOR="is_voip">This package provides VoIP telephone service</LABEL>      <FIELDSET ID="voip"> +      <LABEL FOR="voip_sessions">Number of simultaneous calls possible</LABEL> +      <INPUT NAME="voip_sessions" ID="voip_sessions">        <& .checkbox, 'voip_lastmile' &>        <LABEL FOR="voip_lastmile">Do you also provide last-mile connectivity?</LABEL>      </FIELDSET> @@ -145,7 +147,7 @@ function save_changes() {    }    parent_input.value = JSON.stringify(data);    // update the display -  parent.show_fcc_options(); +  parent.show_fcc_options(parent_input.id);    parent.cClick(); //overlib  } @@ -179,6 +181,11 @@ function enable_fieldset(fieldset_id) {        form.elements['phone1'].disabled = (this.value == '');      }    ); +  addEventListener(form.elements['is_phone'], 'change',  +    function() { +      form.elements['phone_wholesale'].dispatchEvent( new Event('change') ); +    } +  );    // load data from the parent form and trigger handlers    for(var i = 0; i < form.elements.length; i++) { diff --git a/httemplate/search/477.html b/httemplate/search/477.html index 68493377d..26bd9f33b 100644 --- a/httemplate/search/477.html +++ b/httemplate/search/477.html @@ -36,20 +36,19 @@ a.download {    float: right;  }  </STYLE> -% foreach my $partnum (@partnums) { -%   $cgi->param('parts', $partnum); +% foreach my $partname (@partnames) { +%   $cgi->param('parts', $partname);  %   $cgi->param('type', 'csv');  <table class="fcc477part">    <caption> -    <span class="parttitle">Part <% $partnum %></span> +    <span class="parttitle"><% $parttitle{$partname} %></span>      <a class="download" href="<% $cgi->self_url %>">Download</a>    </caption> -%   my $header = ".header$partnum"; -%   my $data = $parts{$partnum}; +%   my $header = ".header_$partname"; +%   my $data = $parts{$partname};    <thead>      <& $header &>    </thead> -%   #XXX column headings  %   foreach my $row (@$data) {    <tr>  %     foreach my $item (@$row) { @@ -58,7 +57,7 @@ a.download {    </tr>  %   }  </table> -% } # foreach $partnum +% } # foreach $partname  <& /elements/footer.html &>  <%init>  die "access denied" @@ -80,10 +79,10 @@ if ($cgi->param('agentnum') =~ /^(\d+)$/ ) {    $agentnum = $1;  }  my $date = parse_datetime($cgi->param('date')) || time; -my @partnums = grep /^\d+$/, $cgi->param('parts'); -foreach my $partnum (@partnums) { -  my $method = "part$partnum"; -  $parts{$partnum} ||= FS::Report::FCC_477->$method( +my @partnames = grep /^\w+$/, $cgi->param('parts'); +foreach my $partname (@partnames) { +  my $method = "report_$partname"; +  $parts{$partname} ||= FS::Report::FCC_477->$method(      date      => $date,      agentnum  => $agentnum    ); @@ -93,11 +92,11 @@ $m->cache->set($session, \%parts, '1h');  my $title = 'FCC Form 477 Data - ' . time2str('%b %o, %Y', $date);  if ( $cgi->param('type') eq 'csv' ) { -  my $partnum = $partnums[0]; # ignore any beyond the first -  my $data = $parts{$partnum}; +  my $partname = $partnames[0]; # ignore any beyond the first +  my $data = $parts{$partname};    my $csv = Text::CSV_XS->new({ eol => "\r\n" }); # i think -  my $filename = time2str('%Y-%m-%d', $date) . '-part' . $partnum . '.csv'; +  my $filename = time2str('%Y-%m-%d', $date) . '-'. $partname . '.csv';    http_header('Content-Type' => 'text/csv');    http_header('Content-Disposition' => qq(attachment;filename="$filename")); @@ -111,7 +110,7 @@ if ( $cgi->param('type') eq 'csv' ) {  }  </%init> -<%def .header6> +<%def .header_fixed_broadband>    <TR CLASS="head">      <TD ROWSPAN=2>Census Tract</TD>      <TD ROWSPAN=2>Technology</TD> @@ -125,30 +124,18 @@ if ( $cgi->param('type') eq 'csv' ) {      <TD>Consumer</TD>    </TR>  </%def> -<%def .header7> +<%def .header_fixed_voice>    <TR CLASS="head"> -    <TD ROWSPAN=2>State</TD> -    <TD COLSPAN=2>Speed (Mbps)</TD> -    <TD COLSPAN=2>Subscriptions</TD> +    <TD ROWSPAN=2>Census Tract</TD> +    <TD ROWSPAN=2>VoIP?</TD> +    <TD COLSPAN=2>Lines/Subscriptions</TD>    </TR>    <TR CLASS="subhead"> -    <TD>Down</TD> -    <TD>Up</TD>      <TD>Total</TD>      <TD>Consumer</TD>    </TR>  </%def> -<%def .header8> -  <TR CLASS="head"> -    <TD ROWSPAN=2>State</TD> -    <TD COLSPAN=2>Subscriptions</TD> -  </TR> -  <TR CLASS="subhead"> -    <TD>Total</TD> -    <TD>Direct</TD> -  </TR> -</%def> -<%def .header9> +<%def .header_local_phone>    <TR CLASS="head">      <TD ROWSPAN=3>State</TD>      <TD COLSPAN=2>Wholesale</TD> @@ -183,7 +170,7 @@ if ( $cgi->param('type') eq 'csv' ) {      <TD>Wireless</TD>    </TR>  </%def> -<%def .header10> +<%def .header_voip>    <TR CLASS="head">      <TD ROWSPAN=2>State</TD>      <TD COLSPAN=2>VoIP OTT</TD> @@ -206,14 +193,29 @@ if ( $cgi->param('type') eq 'csv' ) {      <TD>Other</TD>    </TR>  </%def> -<%def .header11> +<%def .header_mobile_broadband> +%# unimplemented    <TR CLASS="head"> -    <TD ROWSPAN=2>Census Tract</TD> -    <TD ROWSPAN=2>VoIP?</TD> -    <TD COLSPAN=2>Lines/Subscriptions</TD> +    <TD ROWSPAN=2>State</TD> +    <TD COLSPAN=2>Speed (Mbps)</TD> +    <TD COLSPAN=2>Subscriptions</TD>    </TR>    <TR CLASS="subhead"> +    <TD>Down</TD> +    <TD>Up</TD>      <TD>Total</TD>      <TD>Consumer</TD>    </TR>  </%def> +<%def .header_mobile_voice> +%# unimplemented +  <TR CLASS="head"> +    <TD ROWSPAN=2>State</TD> +    <TD COLSPAN=2>Subscriptions</TD> +  </TR> +  <TR CLASS="subhead"> +    <TD>Total</TD> +    <TD>Direct</TD> +  </TR> +</%def> + diff --git a/httemplate/search/report_477.html b/httemplate/search/report_477.html index e3ae69e6f..2a6878ef4 100755 --- a/httemplate/search/report_477.html +++ b/httemplate/search/report_477.html @@ -31,12 +31,12 @@        'label'   => 'Enable parts',        'field'   => 'parts',        'labels'  => { -        6   => 'Part 6 (Fixed Broadband Subscription)', +        fixed_broadband => 'Fixed Broadband Subscription',          #7   => 'Part 7 (Mobile Wireless Broadband Subscription),          #8   => 'Part 8 (Mobile Local Telephone Subscription), -        9   => 'Part 9 (Local Exchange Telephone Subscription)', -        10  => 'Part 10 (Interconnected VoIP Subscription)', -        11  => 'Part 11 (Voice Telephone Subscription Detail)', +        fixed_voice     => 'Voice Telephone Subscription', +        local_phone     => 'Local Exchange Telephone Subscription', +        voip            => 'Interconnected VoIP Subscription',        },        options => [ 6, 9, 10, 11 ],      &>  | 
