X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2FReport%2FFCC_477.pm;h=75ddee0d7c5c6cb12a72f132c30c710f4f7e4345;hb=ffa18709ee8a4d05e18d2d406cf73afe79e52524;hp=79f00e371a5dceefc7ef195dcbcb92c548dd6ea3;hpb=0ce1f788031c5eaac0c620add539730dd87e3113;p=freeside.git diff --git a/FS/FS/Report/FCC_477.pm b/FS/FS/Report/FCC_477.pm index 79f00e371..75ddee0d7 100644 --- a/FS/FS/Report/FCC_477.pm +++ b/FS/FS/Report/FCC_477.pm @@ -4,11 +4,13 @@ use base qw( FS::Report ); use strict; use vars qw( @upload @download @technology @part2aoption @part2boption %states - $DEBUG ); use FS::Record qw( dbh ); -$DEBUG = 1; +use Tie::IxHash; +use Storable; + +our $DEBUG = 0; =head1 NAME @@ -165,8 +167,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 +204,8 @@ sub statenum2state { my $num = shift; $states{$num}; } +### everything above this point is unmaintained ### + =head1 THE "NEW" REPORT (October 2014 and later) @@ -213,6 +215,8 @@ sub statenum2state { =cut +# functions for internal use + sub join_optionnames { join(' ', map { join_optionname($_) } @_); } @@ -242,6 +246,17 @@ sub join_optionname_int { " ON (part_pkg.pkgpart = t_$name.pkgpart)"; } +sub dbaname { + # Returns an sql expression for the DBA name + "COALESCE( deploy_zone.dbaname, + (SELECT value FROM conf WHERE conf.name = 'company_name' + AND (conf.agentnum = deploy_zone.agentnum + OR conf.agentnum IS NULL) + ORDER BY conf.agentnum IS NOT NULL DESC + LIMIT 1) + ) AS dbaname" +} + sub active_on { # Returns a condition to limit packages to those that were setup before a # certain date, and not canceled before that date. @@ -250,48 +265,150 @@ sub active_on { # "suspended as of some past date" is a complicated query.) my $date = shift; "cust_pkg.setup <= $date AND ". - "(cust_pkg.cancel IS NULL OR cust_pkg.cancel > $date)"; + "(cust_pkg.cancel IS NULL OR cust_pkg.cancel > $date) AND ". + "(cust_pkg.change_date IS NULL OR cust_pkg.change_date <= $date)" } sub is_fixed_broadband { - "is_broadband::int = 1 AND technology::int IN(".join(',', - 10, 11, 12, 20, 30, 40, 41, 42, 50, 60, 70, 90, 0 - ).")"; + "is_broadband::int = 1 AND technology::int IN( 10, 11, 12, 20, 30, 40, 41, 42, 50, 60, 70, 90, 0 )" } -=item part6 OPTIONS +sub is_mobile_broadband { + "is_broadband::int = 1 AND technology::int IN( 80, 81, 82, 83, 84, 85, 86, 87, 88)" +} + + +=item report SECTION, OPTIONS + +Returns the report section SECTION (see the C method for section +name strings). OPTIONS may contain the following: + +- date: a timestamp value. Packages that were active on that date will be +counted. -Returns Part 6 of the 2014 FCC 477 data, as an arrayref of arrayrefs. -OPTIONS may contain: -- date: a timestamp value to count active packages as of that date -- agentnum: limit to customers of that agent +- agentnum: limit to packages with this agent. -Part 6 is the broadband subscription detail report. Columns of the -report are: -- census tract -- technology code -- downstream speed -- upstream speed -(the above columns form a key) -- number of subscriptions -- number of consumer-grade subscriptions +- ignore_quantity: if true, package quantities will be ignored (only distinct +packages will be counted). + +The result will be a hashref containing three parallel arrayrefs: +- "data", the columns required by the FCC. +- "detail", a list of the package numbers included in each row's aggregation +- "error", a hashref containing any error status strings in that row. Keys +are error identifiers, values are the messages to show the user. +as well as an informational item: +- "num_errors", the number of rows that contain errors + +=item report_data SECTION, OPTIONS + +Returns only the data, not the detail or error columns. This is the part that +will be submitted to the FCC. =cut -sub part6 { +sub report { + my $class = shift; + my $section = shift; + my %opt = @_; + $opt{detail} = 1; + + # add the error column + my $data = $class->report_data($section, %opt); + my $error = []; + my $detail = []; + my $check_method = $section.'_check'; + my $num_errors = 0; + foreach my $row (@$data) { + if ( $class->can($check_method) ) { # they don't all have these + my $eh = $class->$check_method( $row ); + $num_errors++ if keys(%$eh); + push @$error, $eh + } + push @$detail, pop @$row; # this comes from the query + } + + return +{ + data => $data, + error => $error, + detail => $detail, + num_errors => $num_errors, + }; +} + +sub report_data { my $class = shift; - my %opt = shift; + my $section = shift; + my %opt = @_; + + my $method = $section.'_sql'; + die "Report section '$section' is not implemented\n" + unless $class->can($method); + my $statement = $class->$method(%opt); + + warn $statement if $DEBUG; + my $sth = dbh->prepare($statement); + $sth->execute or die $sth->errstr; + return $sth->fetchall_arrayref; +} + +sub fbd_sql { + my $class = shift; + my %opt = @_; my $date = $opt{date} || time; my $agentnum = $opt{agentnum}; my @select = ( - 'cust_location.censustract', + 'censusblock', + dbaname(), 'technology', + 'CASE WHEN is_consumer IS NOT NULL THEN 1 ELSE 0 END', + 'adv_speed_down', + 'adv_speed_up', + 'CASE WHEN is_business IS NOT NULL THEN 1 ELSE 0 END', + 'cir_speed_down', + 'cir_speed_up', + ); + push @select, 'blocknum' if $opt{detail}; + + my $from = 'deploy_zone_block + JOIN deploy_zone USING (zonenum) + JOIN agent USING (agentnum)'; + my @where = ( + "zonetype = 'B'", + "active_date < $date", + "(expire_date > $date OR expire_date IS NULL)", + ); + push @where, "agentnum = $agentnum" if $agentnum; + + my $order_by = 'censusblock, agentnum, technology, is_consumer, is_business'; + + "SELECT ".join(', ', @select) . " + FROM $from + WHERE ".join(' AND ', @where)." + ORDER BY $order_by + "; +} + +sub fbs_sql { + my $class = shift; + my %opt = @_; + my $date = $opt{date} || time; + my $agentnum = $opt{agentnum}; + my $q = $opt{ignore_quantity} ? '1' : 'COALESCE(cust_pkg.quantity, 1)'; + + my $censustract = "replace(cust_location.censustract, '.', '')"; + + my @select = ( + "$censustract AS censustract", + '(technology - technology % 10) AS media_type', + # media types are multiples of 10 'broadband_downstream', 'broadband_upstream', - 'COUNT(*)', - 'COUNT(is_consumer)', + "SUM($q)", + "SUM(COALESCE(is_consumer,0) * $q)", ); + push @select, "array_to_string(array_agg(pkgnum), ',')" if $opt{detail}; + my $from = 'cust_pkg JOIN cust_location ON (cust_pkg.locationnum = cust_location.locationnum) @@ -308,67 +425,130 @@ sub part6 { is_fixed_broadband() ); push @where, "cust_main.agentnum = $agentnum" if $agentnum; - my $group_by = 'cust_location.censustract, technology, '. - 'broadband_downstream, broadband_upstream '; + my $group_by = "$censustract, technology, broadband_downstream, broadband_upstream "; my $order_by = $group_by; - my $statement = "SELECT ".join(', ', @select) . " + "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 part9 OPTIONS - -Returns Part 9 of the 2014 FCC 477 data. Part 9 is the Local Exchange -Telephone Subscription report. Columns are: - -- state FIPS code (key) -- wholesale switched voice lines -- wholesale unswitched local loops -- end-user total lines -- end-user lines sold in a package with broadband -- consumer-grade lines where you are not the long-distance carrier -- consumer-grade lines where the carrier IS the long-distance carrier -- business-grade lines where you are not the long-distance carrier -- business-grade lines where the carrier IS the long-distance carrier -- end-user lines where you own the local loop facility -- end-user lines where you lease an unswitched local loop from a LEC -- end-user lines resold from another carrier -- end-user lines provided over fiber to the premises -- end-user lines provided over coaxial -- end-user lines provided over fixed wireless +sub fbs_check { + my $class = shift; + my $row = shift; + my %e; + #censustract + if ( length($row->[0]) == 0 ) { + $e{'censustract_null'} = 'The package location has no census tract.'; + } elsif ($row->[0] !~ /^\d{11}$/) { + $e{'censustract_bad'} = 'The census tract must be exactly 11 digits.'; + } -=cut + #technology + if ( length($row->[1]) == 0 ) { + $e{'technology_null'} = 'The package has no technology type.'; + } -sub part9 { + #speeds + if ( length($row->[2]) == 0 or length($row->[3]) == 0 ) { + $e{'speed_null'} = 'The package is missing downstream or upstream speeds.'; + } elsif ( $row->[2] !~ /^\d*(\.\d+)?$/ or $row->[3] !~ /^\d*(\.\d+)?$/ ) { + $e{'speed_bad'} = 'The downstream and upstream speeds must be decimal numbers in Mbps.'; + } elsif ( $row->[2] == 0 or $row->[3] == 0 ) { + $e{'speed_zero'} = 'The downstream and upstream speeds cannot be zero.'; + } + + return \%e; +} + +sub fvs_sql { my $class = shift; - my %opt = shift; + my %opt = @_; my $date = $opt{date} || time; my $agentnum = $opt{agentnum}; + my $q = $opt{ignore_quantity} ? '1' : 'COALESCE(cust_pkg.quantity, 1)'; + my $censustract = "replace(cust_location.censustract, '.', '')"; + + my @select = ( + "$censustract AS censustract", + # VoIP indicator (0 for non-VoIP, 1 for VoIP) + 'COALESCE(is_voip, 0)', + # number of lines/subscriptions + "SUM($q * (CASE WHEN is_voip = 1 THEN 1 ELSE phone_lines END))", + # consumer grade lines/subscriptions + "SUM($q * COALESCE(is_consumer,0) * (CASE WHEN is_voip = 1 THEN voip_sessions ELSE phone_lines END))", + ); + push @select, "array_to_string(array_agg(pkgnum), ',')" if $opt{detail}; + + 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 = "$censustract, COALESCE(is_voip, 0)"; + my $order_by = $group_by; + + "SELECT ".join(', ', @select) . " + FROM $from + WHERE ".join(' AND ', @where)." + GROUP BY $group_by + ORDER BY $order_by + "; + +} + +sub fvs_check { + my $class = shift; + my $row = shift; + my %e; + #censustract + if ( length($row->[0]) == 0 ) { + $e{'censustract_null'} = 'The package location has no census tract.'; + } elsif ($row->[0] !~ /^\d{11}$/) { + $e{'censustract_bad'} = 'The census tract must be exactly 11 digits.'; + } + return \%e; +} + +sub lts_sql { + my $class = shift; + my %opt = @_; + my $date = $opt{date} || time; + my $agentnum = $opt{agentnum}; + my $q = $opt{ignore_quantity} ? '1' : 'COALESCE(cust_pkg.quantity, 1)'; my @select = ( "state.fips", - "SUM(phone_vges)", - "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 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)", - "SUM(CASE WHEN media = 'Fiber' THEN phone_lines ELSE 0 END)", - "SUM(CASE WHEN media = 'Cable Modem' THEN phone_lines ELSE 0 END)", - "SUM(CASE WHEN media = 'Fixed Wireless' THEN phone_lines ELSE 0 END)", + "SUM($q * phone_vges)", + "SUM($q * phone_circuits)", + "SUM($q * phone_lines)", + "SUM($q * (CASE WHEN is_broadband = 1 THEN phone_lines ELSE 0 END))", + "SUM($q * (CASE WHEN is_consumer = 1 AND phone_longdistance IS NULL THEN phone_lines ELSE 0 END))", + "SUM($q * (CASE WHEN is_consumer = 1 AND phone_longdistance = 1 THEN phone_lines ELSE 0 END))", + "SUM($q * (CASE WHEN is_consumer IS NULL AND phone_longdistance IS NULL THEN phone_lines ELSE 0 END))", + "SUM($q * (CASE WHEN is_consumer IS NULL AND phone_longdistance = 1 THEN phone_lines ELSE 0 END))", + "SUM($q * (CASE WHEN phone_localloop = 'owned' THEN phone_lines ELSE 0 END))", + "SUM($q * (CASE WHEN phone_localloop = 'leased' THEN phone_lines ELSE 0 END))", + "SUM($q * (CASE WHEN phone_localloop = 'resale' THEN phone_lines ELSE 0 END))", + "SUM($q * (CASE WHEN media = 'Fiber' THEN phone_lines ELSE 0 END))", + "SUM($q * (CASE WHEN media = 'Cable Modem' THEN phone_lines ELSE 0 END))", + "SUM($q * (CASE WHEN media = 'Fixed Wireless' THEN phone_lines ELSE 0 END))", ); + push @select, "array_to_string(array_agg(pkgnum),',')" if $opt{detail}; + my $from = 'cust_pkg JOIN cust_location ON (cust_pkg.locationnum = cust_location.locationnum) @@ -378,7 +558,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') ; @@ -390,38 +570,53 @@ sub part9 { my $group_by = 'state.fips'; my $order_by = $group_by; - my $statement = "SELECT ".join(', ', @select) . " + "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); } -sub part10 { +# voip_sql has a special case: the fifth column, "Voice with Internet", +# must test whether there are _any_ broadband packages at the same location, +# not just whether this package is both VoIP and broadband. + +sub voip_sql { my $class = shift; - my %opt = shift; + my %opt = @_; my $date = $opt{date} || time; my $agentnum = $opt{agentnum}; + my $q = $opt{ignore_quantity} ? '1' : 'COALESCE(cust_pkg.quantity, 1)'; + + # subquery to test whether there's an is_broadband package at this location + my $broadband_pkg = + "SELECT 1 FROM cust_pkg AS broadband_pkg + WHERE broadband_pkg.locationnum = cust_pkg.locationnum + AND EXISTS(SELECT 1 FROM part_pkg_fcc_option + WHERE fccoptionname = 'is_broadband' + AND part_pkg_fcc_option.pkgpart = broadband_pkg.pkgpart + AND optionvalue = '1') + AND ". active_on( $date ); + + my $has_broadband = "EXISTS($broadband_pkg)"; my @select = ( "state.fips", # OTT, OTT + consumer - "SUM(CASE WHEN (voip_lastmile IS NULL) THEN 1 ELSE 0 END)", - "SUM(CASE WHEN (voip_lastmile IS NULL AND is_consumer = 1) THEN 1 ELSE 0 END)", + "SUM($q * (CASE WHEN (voip_lastmile IS NULL) THEN 1 ELSE 0 END))", + "SUM($q * (CASE WHEN (voip_lastmile IS NULL AND is_consumer = 1) THEN 1 ELSE 0 END))", # non-OTT: total, consumer, broadband bundle, media types - "SUM(CASE WHEN (voip_lastmile = 1) THEN 1 ELSE 0 END)", - "SUM(CASE WHEN (voip_lastmile = 1 AND is_consumer = 1) THEN 1 ELSE 0 END)", - "SUM(CASE WHEN (voip_lastmile = 1 AND is_broadband = 1) THEN 1 ELSE 0 END)", - "SUM(CASE WHEN (voip_lastmile = 1 AND media = 'Copper') THEN 1 ELSE 0 END)", - "SUM(CASE WHEN (voip_lastmile = 1 AND media = 'Cable Modem') THEN 1 ELSE 0 END)", - "SUM(CASE WHEN (voip_lastmile = 1 AND media = 'Fiber') THEN 1 ELSE 0 END)", - "SUM(CASE WHEN (voip_lastmile = 1 AND media = 'Fixed Wireless') THEN 1 ELSE 0 END)", - "SUM(CASE WHEN (voip_lastmile = 1 AND media NOT IN('Copper', 'Fiber', 'Cable Modem', 'Fixed Wireless') ) THEN 1 ELSE 0 END)", + "SUM($q * (CASE WHEN (voip_lastmile = 1) THEN 1 ELSE 0 END))", + "SUM($q * (CASE WHEN (voip_lastmile = 1 AND is_consumer = 1) THEN 1 ELSE 0 END))", + "SUM($q * (CASE WHEN (voip_lastmile = 1 AND $has_broadband) THEN 1 ELSE 0 END))", + "SUM($q * (CASE WHEN (voip_lastmile = 1 AND media = 'Copper') THEN 1 ELSE 0 END))", + "SUM($q * (CASE WHEN (voip_lastmile = 1 AND media = 'Fiber') THEN 1 ELSE 0 END))", + "SUM($q * (CASE WHEN (voip_lastmile = 1 AND media = 'Cable Modem') THEN 1 ELSE 0 END))", + "SUM($q * (CASE WHEN (voip_lastmile = 1 AND media = 'Fixed Wireless') THEN 1 ELSE 0 END))", + "SUM($q * (CASE WHEN (voip_lastmile = 1 AND media NOT IN('Copper', 'Fiber', 'Cable Modem', 'Fixed Wireless') ) THEN 1 ELSE 0 END))", ); + push @select, "array_to_string(array_agg(pkgnum),',')" if $opt{detail}; my $from = 'cust_pkg @@ -430,7 +625,7 @@ sub part10 { JOIN cust_main ON (cust_pkg.custnum = cust_main.custnum) JOIN part_pkg USING (pkgpart) '. join_optionnames_int( - qw( is_voip is_broadband is_consumer voip_lastmile) + qw( is_voip is_consumer voip_lastmile) ). join_optionnames('media') ; @@ -442,65 +637,136 @@ sub part10 { my $group_by = 'state.fips'; my $order_by = $group_by; - my $statement = "SELECT ".join(', ', @select) . " + "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 part11 OPTIONS +sub mbs_sql { + my $class = shift; + my %opt = @_; + my $date = $opt{date} || time; + my $agentnum = $opt{agentnum}; + my $q = $opt{ignore_quantity} ? '1' : 'COALESCE(cust_pkg.quantity, 1)'; -Returns part 11 (voice subscription detail), as above. + my @select = ( + 'state.fips', + 'broadband_downstream', + 'broadband_upstream', + "SUM($q)", + "SUM(COALESCE(is_consumer, 0) * $q)", + ); + push @select, "array_to_string(array_agg(pkgnum),',')" if $opt{detail}; -=cut + my $from = + 'cust_pkg + JOIN cust_location ON (cust_pkg.locationnum = cust_location.locationnum) + JOIN state USING (country, state) + JOIN cust_main ON (cust_pkg.custnum = cust_main.custnum) + JOIN part_pkg USING (pkgpart) '. + join_optionnames_int(qw( + is_broadband technology + is_consumer + )). + join_optionnames(qw(broadband_downstream broadband_upstream)) + ; + my @where = ( + active_on($date), + is_mobile_broadband() + ); + push @where, "cust_main.agentnum = $agentnum" if $agentnum; + my $group_by = 'state.fips, broadband_downstream, broadband_upstream '; + my $order_by = $group_by; -sub part11 { + "SELECT ".join(', ', @select) . " + FROM $from + WHERE ".join(' AND ', @where)." + GROUP BY $group_by + ORDER BY $order_by + "; +} + +sub mvs_sql { my $class = shift; - my %opt = shift; + my %opt = @_; my $date = $opt{date} || time; my $agentnum = $opt{agentnum}; + my $q = $opt{ignore_quantity} ? '1' : 'COALESCE(cust_pkg.quantity, 1)'; 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)' + 'state.fips', + "SUM($q)", + "SUM($q * COALESCE(mobile_direct,0))", ); + push @select, "array_to_string(array_agg(pkgnum),',')" if $opt{detail}; - 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 $from = + 'cust_pkg + JOIN cust_location ON (cust_pkg.locationnum = cust_location.locationnum) + JOIN state USING (country, state) + JOIN cust_main ON (cust_pkg.custnum = cust_main.custnum) + JOIN part_pkg USING (pkgpart) '. + join_optionnames_int(qw( is_mobile mobile_direct) ) ; - my @where = ( active_on($date), - "(is_voip = 1 OR is_phone = 1)", + 'is_mobile = 1' ); push @where, "cust_main.agentnum = $agentnum" if $agentnum; - my $group_by = 'cust_location.censustract, COALESCE(is_voip, 0)'; + my $group_by = 'state.fips'; my $order_by = $group_by; - my $statement = "SELECT ".join(', ', @select) . " + "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 parts + +Returns a Tie::IxHash reference of the internal short names used for the +report sections ('fbd', 'mbs', etc.) to the full names. + +=cut + +tie our %parts, 'Tie::IxHash', ( + fbd => 'Fixed Broadband Deployment', + fbs => 'Fixed Broadband Subscription', + fvs => 'Fixed Voice Subscription', + lts => 'Local Exchange Telephone Subscription', + voip => 'Interconnected VoIP Subscription', + mbd => 'Mobile Broadband Deployment', + mbsa => 'Mobile Broadband Service Availability', + mbs => 'Mobile Broadband Subscription', + mvd => 'Mobile Voice Deployment', + mvs => 'Mobile Voice Subscription', +); + +sub parts { + Storable::dclone(\%parts); +} + +=item part_table SECTION + +Returns the name of the primary table that's aggregated in the report section +SECTION. The last column of the report returned by the L method is +a comma-separated list of record numbers, in this table, that are included in +the report line item. + +=cut + +sub part_table { + my ($class, $part) = @_; + if ($part eq 'fbd') { + return 'deploy_zone_block'; + } else { + return 'cust_pkg'; + } # add other cases as we add more of the deployment/availability reports } 1;