-<% include( 'elements/search.html',
- 'title' => 'FCC Form 477 Results',
- 'html_init' => $html_init,
- 'name' => 'regions',
- 'query' => $sql_query,
- 'count_query' => $count_query,
- 'header' => [
- 'State code',
- 'County code',
- 'Census tract code',
- 'Quantity',
- ],
- 'fields' => [
- sub { my $row = shift; substr($row->censustract, 0, 2) },
- sub { my $row = shift; substr($row->censustract, 2, 3) },
- sub { my $row = shift; substr($row->censustract, 5) },
- 'quantity',
- ],
- 'links' => [
- [ $link, 'censustract' ],
- [ $link, 'censustract' ],
- [ $link, 'censustract' ],
- [ $link, 'censustract' ],
- ],
- )
-%>
+<& /elements/header.html, $title &>
+<STYLE TYPE="text/css">
+table.fcc477part {
+ border-collapse: collapse;
+ border: 1px #777 solid;
+ margin-bottom: 20px;
+}
+table.fcc477part td {
+ padding: 0px 4px;
+ border-left: 1px #777 solid;
+ border-right: 1px #777 solid;
+}
+table.fcc477part tbody td {
+ text-align: right;
+}
+table.fcc477part thead tr.head {
+ text-align: center;
+ vertical-align: top;
+ font-weight: bold;
+ border-top: 1px #777 solid;
+ border-bottom: 1px #777 solid;
+}
+table.fcc477part thead tr.subhead {
+ text-align: center;
+ font-weight: bold;
+ font-size: small;
+ border-top: 1px #777 solid;
+ border-bottom: 1px #777 solid;
+}
+.parttitle {
+ font-weight: bold;
+ font-size: large;
+ float: left;
+}
+.errortitle {
+ font-weight: bold;
+ color: #ff0000;
+}
+tr.error td {
+ background-color: #ffdddd;
+}
+tr.error td.error {
+ text-align: left;
+ border: none;
+}
+tr.error ul {
+ margin: 0px;
+ list-style-image: url("<% $fsurl %>images/cross.png");
+}
+a.download {
+ float: right;
+}
+</STYLE>
+% foreach my $partname (@partnames) {
+% my $this_part = $parts{$partname};
+% $cgi->param('parts', $partname);
+% $cgi->param('type', 'csv');
+<table class="fcc477part">
+ <caption>
+ <span class="parttitle"><% $part_titles->{$partname} %></span>
+% if ( $this_part->{num_errors} > 0 ) {
+% # show error heading while it contains errors
+ <span class="errortitle">
+ <% emt('This section contains [quant,_1,error].', $this_part->{num_errors}) %>
+ </span>
+% }
+ <a class="download" href="<% $cgi->self_url %>">Download</a>
+ </caption>
+% my $header = ".header_$partname";
+% my $data = $this_part->{data};
+% my $error = $this_part->{error};
+ <thead>
+ <& $header &>
+ </thead>
+% my $rownum = 0;
+% foreach my $row (@$data) {
+% my %eh; # error hash
+% if ( $error->[$rownum] ) {
+% %eh = %{ $error->[$rownum] };
+% }
+ <tr<% keys(%eh) ? ' class="error"' : ''%>>
+% my $first = 1;
+% foreach my $item (@$row) {
+ <td>
+% if ($first and $part_link{$partname}) {
+ <a href="<% $part_link{$partname} . "477rownum=$rownum" %>"><% $item || '(empty)' %></a>
+% $first = 0;
+% } else {
+ <% $item %>
+% }
+ </td>
+% } #foreach $item
+% # display errors
+% if ( keys %eh ) {
+ <td class="error"><ul>
+% foreach my $key (sort keys %eh) {
+ <li><% $eh{$key} %></li>
+% }
+ </ul></td>
+% } # if there are errors
+ </tr>
+% $rownum++;
+% } #foreach $row
+</table>
+% } # foreach $partname
+<& /elements/footer.html &>
<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List packages');
-my $curuser = $FS::CurrentUser::CurrentUser;
+my %parts;
+my %part_link;
+# load from cache if possible
+my $session;
+if ( $cgi->param('session') =~ /^(\d+)$/ ) {
+ $session = $1;
+ %parts = %{ $m->cache->get($session) };
+} else {
+ $session = sprintf('%010d%06d', time, int(rand(1000000)));
+ $cgi->param('session', $session);
+}
-die "access denied"
- unless $curuser->access_right('List packages');
-
-my %search_hash = ();
-
-$search_hash{'query'} = $cgi->keywords;
-
-for ( qw(agentnum magic status classnum) ) {
- $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
+my $agentnum;
+if ($cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $agentnum = $1;
}
+my $date = parse_datetime($cgi->param('date')) || time;
+my @partnames = grep /^\w+$/, $cgi->param('parts');
+my $ignore_quantity = ($cgi->param('ignore_quantity') ? 1 : 0);
+foreach my $partname (@partnames) {
+ $parts{$partname} ||= FS::Report::FCC_477->report( $partname,
+ date => $date,
+ agentnum => $agentnum,
+ ignore_quantity => $ignore_quantity,
+ ); # includes error, detail, and data parts
+ my $detail_table = FS::Report::FCC_477->part_table($partname);
+ if ($detail_table eq 'cust_pkg') {
+ my $link = popurl(1).'477_cust_pkg.html?477part='.$partname.";date=$date;";
+ if ($agentnum) {
+ $link .= "agentnum=$agentnum;";
+ }
+ $part_link{$partname} = $link;
+ } # don't include detail links to deploy_blocks, that's pointless
+}
+$m->cache->set($session, \%parts, '1h');
-my @report_option = $cgi->param('report_option')
- if $cgi->param('report_option');
-$search_hash{report_option} = join(',', @report_option) if @report_option;
+my $title = 'FCC Form 477 Data - ' . time2str('%b %o, %Y', $date);
-my $where = join(' OR ', map { "num = $_" } grep { /^\d+$/ } @report_option );
-my @report_option_name = $where ?
- ( map { $_->name }
- qsearch({ 'table' => 'part_pkg_report_option',
- 'hashref' => {},
- 'extra_sql' => "WHERE $where",
- })
- ) :
- ( 'all packages' );
+if ( $cgi->param('type') eq 'csv' ) {
+ my $partname = $partnames[0]; # ignore any beyond the first
+ my $data = $parts{$partname}->{data};
+ my $csv = Text::CSV_XS->new({ eol => "\r\n" }); # i think
-my $html_init = "For <BR>". join(' and <BR>', @report_option_name). "<BR>";
+ my $filename = time2str('%Y-%m-%d', $date) . '-'. $partname . '.csv';
+ http_header('Content-Type' => 'text/csv');
+ http_header('Content-Disposition' => qq(attachment;filename="$filename"));
-my $sql_query = FS::cust_pkg->search_sql(\%search_hash);
+ $m->clear_buffer;
-$sql_query->{select} = 'count(*) as quantity, censustract';
-$sql_query->{extra_sql} =~ /^(.*)(ORDER BY bill)(.*)$/s or die "couldn't parse extra_sql";
-$sql_query->{extra_sql} = "$1 GROUP BY censustract $3";
+ foreach my $row (@$data) {
+ $csv->combine(@$row);
+ $m->print($csv->string);
+ }
+ $m->abort;
+}
-my $count_query = delete($sql_query->{'count_query'});
-$count_query = 'SELECT count(*) FROM (SELECT count(*) FROM cust_pkg '.
- $sql_query->{addl_from}. ' '. $sql_query->{extra_sql}. ') AS foo';
+my $part_titles = FS::Report::FCC_477->parts;
-my $link = 'cust_pkg.cgi?'. $cgi->query_string. ';censustract=';
</%init>
+<%def .header_fbd>
+ <TR CLASS="head">
+ <TD ROWSPAN=2>Census Block</TD>
+ <TD ROWSPAN=2>DBA Name</TD>
+ <TD ROWSPAN=2>Technology</TD>
+ <TD ROWSPAN=2>Consumer?</TD>
+ <TD COLSPAN=2>Advertised Speed (Mbps)</TD>
+ <TD ROWSPAN=2>Business?</TD>
+ <TD COLSPAN=2>Contractual Speed (Mbps)</TD>
+ </TR>
+ <TR CLASS="subhead">
+ <TD>Down</TD>
+ <TD>Up</TD>
+ <TD>Down</TD>
+ <TD>Up</TD>
+ </TR>
+</%def>
+<%def .header_fbs>
+ <TR CLASS="head">
+ <TD ROWSPAN=2>Census Tract</TD>
+ <TD ROWSPAN=2>Technology</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_fvs>
+ <TR CLASS="head">
+ <TD ROWSPAN=2>Census Tract</TD>
+ <TD ROWSPAN=2>VoIP?</TD>
+ <TD COLSPAN=2>Lines/Subscriptions</TD>
+ </TR>
+ <TR CLASS="subhead">
+ <TD>Total</TD>
+ <TD>Consumer</TD>
+ </TR>
+</%def>
+<%def .header_lts>
+ <TR CLASS="head">
+ <TD ROWSPAN=3>State</TD>
+ <TD COLSPAN=2>Wholesale</TD>
+ <TD COLSPAN=12>End User Lines</TD>
+ </TR>
+ <TR CLASS="subhead">
+ <TD ROWSPAN=2>VGEs</TD>
+ <TD ROWSPAN=2>UNE-Ls</TD>
+
+ <TD ROWSPAN=2>Total</TD>
+ <TD ROWSPAN=2>With Broadband</TD>
+ <TD COLSPAN=2>Consumer</TD>
+ <TD COLSPAN=2>Business</TD>
+
+ <TD COLSPAN=3>Local Loop</TD>
+
+ <TD COLSPAN=3>Special Media</TD>
+ </TR>
+
+ <TR CLASS="subhead">
+ <TD> </TD>
+ <TD>+LD</TD>
+ <TD> </TD>
+ <TD>+LD</TD>
+
+ <TD>Owned</TD>
+ <TD>UNE-L</TD>
+ <TD>Resale</TD>
+
+ <TD>Fiber</TD>
+ <TD>Coaxial</TD>
+ <TD>Wireless</TD>
+ </TR>
+</%def>
+<%def .header_voip>
+ <TR CLASS="head">
+ <TD ROWSPAN=3>State</TD>
+ <TD COLSPAN=2>VoIP OTT</TD>
+ <TD COLSPAN=8>VoIP Non-OTT</TD>
+ </TR>
+ <TR CLASS="subhead">
+ <TD ROWSPAN=2>Total</TD>
+ <TD ROWSPAN=2>Consumer</TD>
+
+ <TD ROWSPAN=2>Total</TD>
+ <TD ROWSPAN=2>Consumer</TD>
+ <TD ROWSPAN=2>Bundled</TD>
+ <TD COLSPAN=5>Media Type</TD>
+ </TR>
+ <TR CLASS="subhead">
+ <TD>Copper</TD>
+ <TD>Fiber</TD>
+ <TD>Coaxial</TD>
+ <TD>Wireless</TD>
+ <TD>Other</TD>
+ </TR>
+</%def>
+<%def .header_mbs>
+%# unimplemented
+ <TR CLASS="head">
+ <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_mvs>
+%# 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>
+