summaryrefslogtreecommitdiff
path: root/FS
diff options
context:
space:
mode:
authorMark Wells <mark@freeside.biz>2014-07-31 22:54:08 -0700
committerMark Wells <mark@freeside.biz>2014-07-31 22:54:08 -0700
commit0f359d5480aa1621d73ee802f420e8951abc620d (patch)
tree4bab32c865f9ef7b2bb03247a6be75215cfebf85 /FS
parent6c284750de8fe49d7d4cdc6a9a4fb618697780e2 (diff)
new 477 report: deployment info, combined browse-edit UI, #24047
Diffstat (limited to 'FS')
-rw-r--r--FS/FS/AccessRight.pm3
-rw-r--r--FS/FS/Mason.pm3
-rw-r--r--FS/FS/Record.pm31
-rw-r--r--FS/FS/Report/FCC_477.pm160
-rw-r--r--FS/FS/Schema.pm69
-rw-r--r--FS/FS/deploy_zone.pm221
-rw-r--r--FS/FS/deploy_zone_block.pm126
-rw-r--r--FS/FS/deploy_zone_vertex.pm125
-rw-r--r--FS/FS/part_pkg_fcc_option.pm52
-rw-r--r--FS/MANIFEST6
-rw-r--r--FS/t/deploy_zone.t5
-rw-r--r--FS/t/deploy_zone_block.t5
-rw-r--r--FS/t/deploy_zone_vertex.t5
13 files changed, 718 insertions, 93 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";