[freeside-commits] branch master updated. 04f53daab621710db56b075e1aaf56e7c52f9ba9

Mark Wells mark at 420.am
Mon Oct 10 23:54:21 PDT 2016


The branch, master has been updated
       via  04f53daab621710db56b075e1aaf56e7c52f9ba9 (commit)
      from  49d9ea969069430ef3fe23e5b1ac3599e929bb04 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 04f53daab621710db56b075e1aaf56e7c52f9ba9
Author: Mark Wells <mark at freeside.biz>
Date:   Mon Oct 10 23:54:05 2016 -0700

    export tower/sector data to TowerCoverage API, #39776

diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 66b9a51..a1615b7 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -4139,6 +4139,7 @@ sub tables_hashref {
         'classnum',    'int',     '',      '', '', '',
         'model',   'varchar',     '', $char_d, '', '',
         'revision','varchar', 'NULL', $char_d, '', '',
+        'title',   'varchar', 'NULL', $char_d, '', '', # external id
       ],
       'primary_key'  => 'typenum',
       'unique'       => [ [ 'classnum', 'model', 'revision' ] ],
@@ -4886,6 +4887,10 @@ sub tables_hashref {
         'sector_range', 'decimal', 'NULL',      '', '', '',  #?
         'downtilt',     'decimal', 'NULL',      '', '', '',
         'v_width',          'int', 'NULL',      '', '', '',
+        'power',        'decimal', 'NULL',      '', '', '',
+        'line_loss',    'decimal', 'NULL',      '', '', '',
+        'antenna_gain', 'decimal', 'NULL',     '', '', '',
+        'hardware_typenum', 'int', 'NULL',     '', '', '',
         'db_high',          'int', 'NULL',     '', '', '',
         'db_low',           'int', 'NULL',     '', '', '',
         'image',           'blob', 'NULL',     '', '', '',
@@ -4893,6 +4898,8 @@ sub tables_hashref {
         'east',         'decimal', 'NULL', '10,7', '', '',
         'south',        'decimal', 'NULL', '10,7', '', '',
         'north',        'decimal', 'NULL', '10,7', '', '',
+
+        'title',        'varchar', 'NULL', $char_d,'', '',
      ],
       'primary_key'  => 'sectornum',
       'unique'       => [ [ 'towernum', 'sectorname' ], [ 'ip_addr' ], ],
@@ -4901,6 +4908,10 @@ sub tables_hashref {
                           { columns    => [ 'towernum' ],
                             table      => 'tower',
                           },
+                          { columns    => [ 'hardware_typenum' ],
+                            table      => 'hardware_type',
+                            references => [ 'typenum' ],
+                          },
                         ],
     },
 
diff --git a/FS/FS/hardware_type.pm b/FS/FS/hardware_type.pm
index 615c314..8547312 100644
--- a/FS/FS/hardware_type.pm
+++ b/FS/FS/hardware_type.pm
@@ -40,6 +40,8 @@ to which this device type belongs.
 
 =item revision - revision name/number, subordinate to model
 
+=item title - external ID
+
 =back
 
 =head1 METHODS
@@ -104,6 +106,7 @@ sub check {
     || $self->ut_foreign_key('classnum', 'hardware_class', 'classnum')
     || $self->ut_text('model')
     || $self->ut_textn('revision')
+    || $self->ut_textn('title')
   ;
   return $error if $error;
 
diff --git a/FS/FS/part_export/tower_towercoverage.pm b/FS/FS/part_export/tower_towercoverage.pm
new file mode 100644
index 0000000..5d3f835
--- /dev/null
+++ b/FS/FS/part_export/tower_towercoverage.pm
@@ -0,0 +1,420 @@
+package FS::part_export::tower_towercoverage;
+
+use strict;
+use base qw( FS::part_export );
+use FS::Record qw(qsearch qsearchs dbh);
+use FS::hardware_class;
+use FS::hardware_type;
+
+use vars qw( %options %info
+             %frequency_id %antenna_type_id );
+
+use Color::Scheme;
+use LWP::UserAgent;
+use XML::LibXML::Simple qw(XMLin);
+use Data::Dumper;
+
+# note this is not https
+our $base_url = 'http://api.towercoverage.com/towercoverage.asmx/';
+
+our $DEBUG = 0;
+our $me = '[towercoverage.com]';
+
+sub debug {
+  warn "$me ".join("\n", at _)."\n"
+    if $DEBUG;
+}
+
+# hardware class to use for antenna defs
+my $classname = 'TowerCoverage.com antenna';
+
+tie %options, 'Tie::IxHash', (
+  'debug'       => { label => 'Enable debugging', type => 'checkbox' },
+
+  'Account'     => { label  => 'Account ID' },
+  'key'         => { label  => 'API key' },
+  'use_coverage'  => { label => 'Enable coverage maps', type => 'checkbox' },
+  'FrequencyID' => { label    => 'Frequency band',
+                     type     => 'select',
+                     options  => [ keys(%frequency_id) ],
+                     option_labels => \%frequency_id,
+                   },
+  'MaximumRange'  => { label => 'Maximum range (miles)', default => '10' },
+  '1'           => { type => 'title', label => 'Client equipment' },
+  'ClientAverageAntennaHeight' => { label => 'Typical antenna height (feet)' },
+  'ClientAntennaGain'   => { label => 'Antenna gain (dB)' },
+  'RxLineLoss'          => { label => 'Line loss (dB)',
+                             default => 0,
+                           },
+  '2'           => { type => 'title', label => 'Performance requirements' },
+  'WeakRxThreshold'     => { label => 'Low quality (dBm)', },
+  'StrongRxThreshold'   => { label => 'High quality (dBm)', },
+  'RequiredReliability' => { label => 'Reliability %',
+                             default => 70
+                           },
+);
+
+%info = (
+  'svc'     => [qw( tower_sector )],
+  'desc'    => 'TowerCoverage.com coverage mapping and site qualification',
+  'options' => \%options,
+  'no_machine' => 1,
+  'notes'   => <<'END',
+Export tower/sector configurations to TowerCoverage.com for coverage map
+generation.
+END
+);
+
+sub insert {
+  my $self = shift;
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+
+  my $error = $self->SUPER::insert(@_);
+  return $error if $error;
+
+  my $hwclass = _hardware_class();
+  if (!$hwclass) {
+
+    $hwclass = FS::hardware_class->new({ classname => $classname });
+    $error = $hwclass->insert;
+    if ($error) {
+      dbh->rollback if $oldAutoCommit;
+      return "error creating hardware class for antenna types: $error";
+    }
+
+    foreach my $id (keys %antenna_type_id) {
+      my $name = $antenna_type_id{$id};
+      my $hardware_type = FS::hardware_type->new({
+        classnum  => $hwclass->classnum,
+        model     => $name,
+        title     => $id,
+      });
+      $error = $hardware_type->insert;
+      if ($error) {
+        dbh->rollback if $oldAutoCommit;
+        return "error creating hardware class for antenna types: $error";
+      }
+    }
+  }
+  dbh->commit if $oldAutoCommit;
+  '';
+}
+
+sub export_insert {
+  my ($self, $sector) = @_;
+
+  return unless $self->option('use_coverage');
+  local $DEBUG = $self->option('debug') ? 1 : 0;
+
+  my $tower = $sector->tower;
+  my $height_m = sprintf('%.0f', ($sector->height || $tower->height) / 3.28);
+  my $clientheight_m = sprintf('%.0f', $self->option('ClientAverageAntennaHeight') / 3.28);
+  my $maximumrange_km = sprintf('%.0f', $self->option('MaximumRange') * 1.61);
+  my $strongmargin = $self->option('StrongRxThreshold')
+                   - $self->option('WeakRxThreshold');
+
+  my $scheme = Color::Scheme->new->from_hex($tower->color || '00FF00');
+
+  my $antenna = qsearchs('hardware_type', {
+    typenum => $sector->hardware_typenum
+  });
+  return "antenna type required" unless $antenna;
+
+  # - ALL parameters must be present (or it throws a generic 500 error).
+  # - ONLY Coverageid and TowerSiteid are allowed to be empty.
+  # - ALL parameter names are case sensitive.
+  # - ALL numeric parameters are required to be integers, except for the
+  #   coordinates, line loss factors, and center frequency.
+  # - Export options (like RxLineLoss) have a problem where if they're set
+  #   to numeric zero, they get removed; make sure we actually send zero.
+  my $data = [
+    'Account'                     => $self->option('Account'),
+    'key'                         => $self->option('key'),
+    'Coverageid'                  => $sector->title,
+    'Coveragename'                => $sector->description,
+    'TowerSiteid'                 => '',
+    'Latitude'                    => $tower->latitude,
+    'Longitude'                   => $tower->longitude,
+    'AntennaHeight'               => $height_m,
+    'ClientAverageAntennaHeight'  => $clientheight_m,
+    'ClientAntennaGain'           => $self->option('ClientAntennaGain'),
+    'RxLineLoss'                  => sprintf('%.1f', $self->option('RxLineLoss')),
+    'AntennaType'                 => $antenna->title,
+    'AntennaAzimuth'              => int($sector->direction),
+    # note that TowerCoverage bases their coverage map on the antenna
+    # radiation pattern, not on this number.
+    'BeamwidthFilter'             => $sector->width,
+    'AntennaTilt'                 => int($sector->downtilt),
+    'AntennaGain'                 => int($sector->antenna_gain),
+    'Frequency'                   => $self->option('FrequencyID'),
+    'ExactCenterFrequency'        => $sector->freq_mhz,
+    'TXPower'                     => int($sector->power),
+    'TxLineLoss'                  => sprintf('%.1f', $sector->line_loss),
+    'RxThreshold'                 => $self->option('WeakRxThreshold'),
+    'RequiredReliability'         => $self->option('RequiredReliability'),
+    'StrongSignalMargin'          => $strongmargin,
+    'StrongSignalColor'           => ($scheme->colors)[0],
+    'WeakSignalColor'             => ($scheme->colors)[2],
+    'Opacity'                     => 50,
+    'MaximumRange'                => $maximumrange_km,
+    # this could be selectable but there's no reason to do that
+    'RenderingQuality'            => 3,
+    'UseLandCover'                => 1,
+    'UseTwoRays'                  => 1,
+    'CreateViewshed'              => 0,
+  ];
+  debug Dumper($data);
+  $self->http_queue(
+    'action'    => 'insert',
+    'path'      => 'CoverageAPI',
+    'sectornum' => $sector->sectornum,
+    'data'      => $data
+  );
+
+}
+
+sub export_replace { # do the same thing as insert
+  my $self = shift;
+  $self->export_insert(@_);
+}
+
+sub export_delete { '' }
+
+=item http_queue
+
+Queue a job to send an API request.
+Takes arguments:
+'action'    => what we're doing (for triggering after_* callback)
+'path'      => the path under TowerCoverage.asmx/
+'sectornum' => the sectornum
+'data'      => arrayref/hashref of params to send 
+to which it will add
+'exportnum' => the exportnum
+
+=cut
+ 
+sub http_queue {
+  my $self = shift;
+  my $queue = new FS::queue { 'job' => "FS::part_export::tower_towercoverage::http" };
+  return $queue->insert(
+    exportnum => $self->exportnum,
+    @_
+  );
+}
+
+sub http {
+  my %params = @_;
+  my $self = FS::part_export->by_key($params{'exportnum'});
+  local $DEBUG = $self->option('debug') ? 1 : 0;
+
+  local $FS::tower_sector::noexport_hack = 1; # avoid recursion
+
+  my $url = $base_url . $params{'path'};
+
+  my $ua = LWP::UserAgent->new;
+
+  # URL is the same for insert and replace.
+  my $req = HTTP::Request::Common::POST( $url, $params{'data'} );
+  debug("sending $url", $req->content);
+  my $response = $ua->request($req);
+
+  die $response->error_as_HTML if $response->is_error;
+  debug "received ".$response->decoded_content;
+
+  # throws exception on parse error
+  my $response_data = XMLin($response->decoded_content);
+  my $method = "after_" . $params{action};
+  if ($self->can($method)) {
+    # should be some kind of event handler, that would be sweet
+    my $sector = FS::tower_sector->by_key($params{'sectornum'});
+    $self->$method($sector, $response_data);
+  }
+}
+
+sub after_insert {
+  my ($self, $sector, $data) = @_;
+  my ($png_path, $kml_path) = split("\n", $data->{content});
+  die "$me no coverage map paths in response\n" unless $png_path;
+  if ( $png_path =~ /(\d+).png$/ ) {
+    $sector->set('title', $1);
+    my $error = $sector->replace;
+    die $error if $error;
+  } else {
+    die "$me can't parse map path '$png_path'\n";
+  }
+}
+
+sub _hardware_class {
+  qsearchs( 'hardware_class', { classname => $classname });
+}
+
+sub get_antenna_types {
+  my $hardware_class = _hardware_class() or return;
+  # return hardware typenums, not TowerCoverage IDs.
+  tie my %t, 'Tie::IxHash';
+
+  foreach my $type (qsearch({
+    table     => 'hardware_type',
+    hashref   => { 'classnum' => $hardware_class->classnum },
+    order_by  => ' order by title::integer'
+  })) {
+    $t{$type->typenum} = $type->model;
+  }
+
+  return \%t;
+}
+
+sub export_links {
+  my $self = shift;
+  my ($sector, $arrayref) = @_;
+  if ( $sector->title =~ /^\d+$/ ) {
+    my $link = "http://www.towercoverage.com/En-US/Dashboard/editcoverages/".
+               $sector->title;
+    push @$arrayref, qq!<a href="$link" target="_blank">TowerCoverage map</a>!;
+  }
+}
+
+# we can query this from them, but that requires the account id and key...
+# XXX do some jquery magic in the UI to grab the account ID and key from
+# those fields, and then look it up right there
+
+BEGIN {
+  tie our %frequency_id, 'Tie::IxHash', (
+    1 => "2400 MHz",
+    2 => "5700 MHz",
+    3 => "5300 MHz",
+    4 => "900 MHz",
+    5 => "3650 MHz",
+    12 => "584 MHz",
+    13 => "24000 MHz",
+    14 => "11000 MHz Licensed",
+    15 => "815 MHz",
+    16 => "860 MHz",
+    17 => "1800 MHz CDMA 3G",
+    18 => "18000 MHz Licensed",
+    19 => "1700 MHz",
+    20 => "2100 MHz AWS",
+    21 => "2500-2700 MHz EBS/BRS",
+    22 => "6000 MHz Licensed",
+    23 => "476 MHz",
+    24 => "4900 MHz - Public Safety",
+    25 => "2300 MHz",
+    28 => "7000 MHz 4PSK",
+    29 => "12000 MHz 4PSK",
+    30 => "60 MHz",
+    31 => "260 MHz",
+    32 => "70 MHz",
+    34 => "155 MHz",
+    35 => "365 MHz",
+    36 => "435 MHz",
+    38 => "3500 MHz",
+    39 => "750 MHz",
+    40 => "27 MHz",
+    41 => "10000 MHz",
+    42 => "10250 Mhz",
+    43 => "10250 Mhz",
+    44 => "160 MHz",
+    45 => "700 MHz",
+    46 => "722 MHz",
+    47 => "38000 Mhz",
+    49 => "551 MHz",
+    50 => "600 MHz",
+    51 => "2300 MHz",
+    52 => "5100 MHz",
+    53 => "1900Mhz",
+  );
+
+  # there has to be a better way to handle this. load it during upgrade?
+  # provide a proxy method like get_dids?
+
+  tie our %antenna_type_id, 'Tie::IxHash', (
+    1 => 'Generic - Omni',
+    5 => 'Generic - 120 Degree',
+    8 => 'Generic - 45 Degree Panel',
+    9 => 'Generic - 60 Degree Panel',
+    10 => 'Generic - 60 Degree x 8 Sectors',
+    11 => 'Generic - 90 Degree',
+    12 => 'Alvarion 3.65 WiMax Base Satation',
+    24 => 'Tranzeo - 3.5 GHz 17db 60 Sector',
+    31 => 'Alpha - 2.3 2033 Omni',
+    32 => "PMP450 - 60° Sector",
+    33 => "PMP450 - 90° Sector",
+    34 => 'PMP450 - SM Panel',
+    36 => 'KPPA - 2GHZDP90S-45 17 dBi',
+    37 => 'KPPA - 2GHZDP120S-45 14.2 dBi',
+    38 => 'KPPA - 3GHZDP60S-45 16.3 dBi',
+    39 => 'KPPA - 3GHZDP90S-45 16.7 dBi',
+    40 => 'KPPA - 3GHZDP120S-45 14.8 dBi',
+    41 => 'KPPA - 5GHZDP40S-17 18.2 dBi',
+    42 => 'KPPA - 5GHZDP60S 17.7 dBi',
+    43 => 'KPPA - 5GHZDP60S-17 18.2 dBi',
+    44 => 'KPPA - 5GHZDP90S 17 dBi',
+    45 => 'KPPA - 5GHZDP120S 16.3 dBi',
+    46 => 'KPPA - OMNI-DP-2 13 dBi',
+    47 => 'KPPA - OMNI-DP-2.4-45 10.7 dBi',
+    48 => 'KPPA - OMNI-DP-3 13 dBi',
+    49 => 'KPPA - OMNI-DP-3-45 11 dBi',
+    51 => 'KPPA - OMNI-DP-5 14 dBi',
+    53 => 'Telrad - 65 Degree 3.65 Ghz',
+    54 => 'KPPA - 2GHZDP60S-17-45 15.1 dBi',
+    55 => 'KPPA - 2GHZDP60S-45 17.9 dBi',
+    56 => 'UBNT - AG-2G20',
+    57 => 'UBNT - AG-5G23',
+    58 => 'UBNT - AG-5G27',
+    59 => 'UBNT - AM-2G15-120',
+    60 => 'UBNT - AM-2G16-90',
+    61 => 'UBNT - AM-3G18-120',
+    62 => 'UBNT - AM-5G16-120',
+    63 => 'UBNT - AM-5G17-90',
+    64 => 'UBNT - AM-5G19-120',
+    65 => 'UBNT - AM-5G20-90',
+    66 => 'UBNT - AM-9G15-90',
+    67 => 'UBNT - AMO-2G10',
+    68 => 'UBNT - AMO-2G13',
+    69 => 'UBNT - AMO-5G10',
+    70 => 'UBNT - AMO-5G13',
+    71 => 'UBNT - AMY-9M16',
+    72 => 'UBNT - LOCOM2',
+    73 => 'UBNT - LOCOM5',
+    74 => 'UBNT - LOCOM9',
+    75 => 'UBNT - NB-2G18',
+    76 => 'UBNT - NB-5G22',
+    77 => 'UBNT - NB-5G25',
+    78 => 'UBNT - NBM3',
+    79 => 'UBNT - NBM9',
+    80 => 'UBNT - NSM2',
+    81 => 'UBNT - NSM3',
+    82 => 'UBNT - NSM5',
+    83 => 'UBNT - NSM9',
+    84 => 'UBNT - PBM3',
+    85 => 'UBNT - PBM5',
+    86 => 'UBNT - PBM10',
+    87 => 'UBNT - RD-2G23',
+    88 => 'UBNT - RD-3G25',
+    89 => 'UBNT - RD-5G30',
+    90 => 'UBNT - RD-5G34',
+    92 => 'TerraWave - 2.3-2.7 18db 65-Degree Panel',
+    93 => 'UBNT - AM-M521-60-AC',
+    94 => 'UBNT - AM-M522-45-AC',
+    101 => 'RF Elements - SH-TP-5-30',
+    104 => 'RF Elements - SH-TP-5-40',
+    105 => 'RF Elements - SH-TP-5-50',
+    106 => 'RF Elements - SH-TP-5-60',
+    107 => 'RF Elements - SH-TP-5-70',
+    108 => 'RF Elements - SH-TP-5-80',
+    109 => 'RF Elements - SH-TP-5-90',
+    110 => 'UBNT - Test',
+    111 => '60 Titanium',
+    112 => '3.65GHz - 6x6',
+    113 => 'AW3015-t0-c4(EOS)',
+    114 => 'AW3035 (EOS)',
+    122 => 'RF Elements - SEC-CC-5-20',
+    135 => 'RF Elements - SEC-CC-2-14',
+    137 => 'RF Elements - SEC-CC-5-17',
+    168 => 'KPPA - Mimosa - 5GHZZHV4P65S-17',
+  );
+}
+
+1;
diff --git a/FS/FS/tower_sector.pm b/FS/FS/tower_sector.pm
index 08e8cc0..2e92323 100644
--- a/FS/FS/tower_sector.pm
+++ b/FS/FS/tower_sector.pm
@@ -1,6 +1,7 @@
 package FS::tower_sector;
 use base qw( FS::Record );
 
+use FS::Record qw(dbh qsearch);
 use Class::Load qw(load_class);
 use File::Path qw(make_path);
 use Data::Dumper;
@@ -8,6 +9,8 @@ use Cpanel::JSON::XS;
 
 use strict;
 
+our $noexport_hack = 0;
+
 =head1 NAME
 
 FS::tower_sector - Object methods for tower_sector records
@@ -118,9 +121,26 @@ otherwise returns false.
 
 sub insert {
   my $self = shift;
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
   my $error = $self->SUPER::insert;
   return $error if $error;
 
+  unless ($noexport_hack) {
+    foreach my $part_export ($self->part_export) {
+      my $error = $part_export->export_insert($self);
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "exporting to ".$part_export->exporttype.
+               " (transaction rolled back): $error";
+      }
+    }
+  }
+
+  # XXX exportify
   if (scalar($self->need_fields_for_coverage) == 0) {
     $self->queue_generate_coverage;
   }
@@ -128,7 +148,27 @@ sub insert {
 
 sub replace {
   my $self = shift;
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
   my $old = shift || $self->replace_old;
+  my $error = $self->SUPER::replace($old);
+  return $error if $error;
+
+  unless ( $noexport_hack ) {
+    foreach my $part_export ($self->part_export) {
+      my $error = $part_export->export_replace($self, $old);
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "exporting to ".$part_export->exporttype.
+               " (transaction rolled back): $error";
+      }
+    }
+  }
+
+  #XXX exportify
   my $regen_coverage = 0;
   if ( !$self->get('no_regen') ) {
     foreach (qw(height freq_mhz direction width downtilt
@@ -138,8 +178,6 @@ sub replace {
     }
   }
 
-  my $error = $self->SUPER::replace($old);
-  return $error if $error;
 
   if ($regen_coverage) {
     $self->queue_generate_coverage;
@@ -155,11 +193,31 @@ Delete this record from the database.
 sub delete {
   my $self = shift;
 
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
   #not the most efficient, not not awful, and its not like deleting a sector
   # with customers is a common operation
   return "Can't delete a sector with customers" if $self->svc_broadband;
 
-  $self->SUPER::delete;
+  unless ($noexport_hack) {
+    foreach my $part_export ($self->part_export) {
+      my $error = $part_export->export_delete($self);
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "exporting to ".$part_export->exporttype.
+               " (transaction rolled back): $error";
+      }
+    }
+  }
+
+  my $error = $self->SUPER::delete;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
 }
 
 =item check
@@ -185,13 +243,19 @@ sub check {
     || $self->ut_numbern('v_width')
     || $self->ut_numbern('downtilt')
     || $self->ut_floatn('sector_range')
-    || $self->ut_numbern('db_high')
-    || $self->ut_numbern('db_low')
+    || $self->ut_decimaln('power')
+    || $self->ut_decimaln('line_loss')
+    || $self->ut_decimaln('antenna_gain')
+    || $self->ut_numbern('hardware_typenum')
+    || $self->ut_textn('title')
+    # all of these might get relocated as part of coverage refactoring
     || $self->ut_anything('image')
     || $self->ut_sfloatn('west')
     || $self->ut_sfloatn('east')
     || $self->ut_sfloatn('south')
     || $self->ut_sfloatn('north')
+    || $self->ut_numbern('db_high')
+    || $self->ut_numbern('db_low')
   ;
   return $error if $error;
 
@@ -229,6 +293,7 @@ Returns a list of required fields for the coverage map that aren't yet filled.
 =cut
 
 sub need_fields_for_coverage {
+  # for now assume exports require all of this
   my $self = shift;
   my $tower = $self->tower;
   my %fields = (
@@ -238,7 +303,8 @@ sub need_fields_for_coverage {
     downtilt  => 'Downtilt',
     width     => 'Horiz. width',
     v_width   => 'Vert. width',
-    db_high   => 'High quality',
+    db_high   => 'High quality signal margin',
+    db_low    => 'Low quality signal margin',
     latitude  => 'Latitude',
     longitude => 'Longitude',
   );
@@ -257,10 +323,12 @@ Starts a job to recalculate the coverage map.
 
 =cut
 
+# XXX move to an export
+
 sub queue_generate_coverage {
   my $self = shift;
   my $need_fields = join(',', $self->need_fields_for_coverage);
-  return "Sector needs fields $need_fields" if $need_fields;
+  return "$need_fields required" if $need_fields;
   $self->set('no_regen', 1); # avoid recursion
   if ( length($self->image) > 0 ) {
     foreach (qw(image west south east north)) {
@@ -277,6 +345,28 @@ sub queue_generate_coverage {
 
 =back
 
+=head1 CLASS METHODS
+
+=over 4
+
+=item part_export
+
+Returns all sector exports. Eventually this may be refined to the level
+of enabling exports on specific sectors.
+
+=cut
+
+sub part_export {
+  my $info = $FS::part_export::exports{'tower_sector'} or return;
+  my @exporttypes = map { dbh->quote($_) } keys %$info or return;
+  qsearch({
+    'table'     => 'part_export',
+    'extra_sql' => 'WHERE exporttype IN(' . join(',', @exporttypes) . ')'
+  });
+}
+
+=back
+
 =head1 SUBROUTINES
 
 =over 4
diff --git a/httemplate/edit/process/tower.html b/httemplate/edit/process/tower.html
index 588a68e..cfbb4ff 100644
--- a/httemplate/edit/process/tower.html
+++ b/httemplate/edit/process/tower.html
@@ -4,7 +4,8 @@
     process_o2m => { 'table'  => 'tower_sector',
                      'fields' => [qw(
                        sectorname ip_addr height freq_mhz direction width
-                       downtilt v_width db_high db_low 
+                       downtilt v_width db_high db_low power line_loss
+                       antenna_gain hardware_typenum
                        sector_range
                      )],
                    },
diff --git a/httemplate/elements/tr-tower_sector.html b/httemplate/elements/tr-tower_sector.html
deleted file mode 100644
index 871c7fd..0000000
--- a/httemplate/elements/tr-tower_sector.html
+++ /dev/null
@@ -1,24 +0,0 @@
-%   unless ( $opt{'js_only'} ) {
-
-      <% include('tr-td-label.html', %opt) %>
-        <TD <% $cell_style %>>
-
-%   }
-%
-            <% include( '/elements/sector.html', %opt ) %>
-%
-%   unless ( $opt{'js_only'} ) {
-
-        </TD>
-      </TR>
-
-%   }
-<%init>
-
-my( %opt ) = @_;
-
-my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
-
-$opt{'label'} ||= 'Sector';
-
-</%init>
diff --git a/httemplate/elements/tr-tower_sectors.html b/httemplate/elements/tr-tower_sectors.html
index 4e8f3fb..106fc76 100644
--- a/httemplate/elements/tr-tower_sectors.html
+++ b/httemplate/elements/tr-tower_sectors.html
@@ -1,3 +1,11 @@
+<%shared>
+# kind of a hack...
+my ($export) = FS::tower_sector->part_export;
+my $antenna_types; # will be an ordered hash
+if ($export and $export->can('get_antenna_types')) {
+  $antenna_types = $export->get_antenna_types;
+}
+</%shared>
 <%init>
 my %opt = @_;
 my $tower = $opt{'object'};
@@ -7,8 +15,9 @@ my $cgi = $opt{'cgi'};
 my $tabcounter = 0;
 
 my @fields = qw(
-  sectorname ip_addr height freq_mhz direction width tilt v_width db_high
-  db_low sector_range
+  sectorname ip_addr height freq_mhz direction width downtilt v_width
+  db_high db_low sector_range
+  power line_loss antenna_gain hardware_typenum
 );
 
 my @sectors;
@@ -74,6 +83,11 @@ my $id = $opt{id} || $opt{field} || 'sectornum';
     border: none;
     text-align: left;
   }
+  .ui-tabs p {
+    margin-top: 8px;
+    margin-bottom: 8px;
+  }
+
 </style>
 
 
@@ -216,6 +230,38 @@ $(function() {
   </p>
 
   <p>
+    <label for="<% $id %>_power"><% emt('Transmit power') %></label>
+    <input size="3"
+           id="<% $id %>_power"
+           name="<% $id %>_power"
+           value="<% $sector->power |h %>">
+    <% emt('dBm') %><br>
+    <label for="<% $id %>_antenna_gain">+ </label>
+    <input size="3"
+           id="<% $id %>_antenna_gain"
+           name="<% $id %>_antenna_gain"
+           value="<% $sector->antenna_gain |h %>">
+    <% emt('dB antenna gain') %><br>
+    <label for="<% $id %>_line_loss">– </label>
+    <input size="3"
+           id="<% $id %>_line_loss"
+           name="<% $id %>_line_loss"
+           value="<% $sector->line_loss |h %>">
+    <% emt('dB line loss') %>
+
+% if ( $antenna_types ) {
+  <p>
+    <label for="<% $id %>_hardware_typenum"><% emt('Antenna type') %></label>
+    <& /elements/select.html,
+      field   => $id.'_hardware_typenum',
+      options => [ '', keys %$antenna_types ],
+      labels  => $antenna_types,
+      curr_value => $sector->hardware_typenum,
+    &>
+  </p>
+% }
+% # this next section might not be necessary if you enter an antenna type
+  <p> 
     <label for="<% $id %>_width"><% emt('Horizontal beam') %></label>
     <input size="3"
            id="<% $id %>_width"
@@ -229,7 +275,7 @@ $(function() {
   </p>
 
   <label><% emt('Signal margin') %></label>
-      <div style="display: inline-block; vertical-align: top">
+  <div style="display: inline-block; vertical-align: top">
       <input class="dbspinner"
              size="4"
              id="<% $id %>_db_high"
@@ -244,7 +290,7 @@ $(function() {
              name="<% $id %>_db_low"
              value="<% $sector->db_low |h %>">
       <% emt('dB (low quality)') %>
-      </div>
+  </div>
 
 </div>
 </%def>
diff --git a/httemplate/search/tower-map.html b/httemplate/search/tower-map.html
index 559d83d..d87e19e 100755
--- a/httemplate/search/tower-map.html
+++ b/httemplate/search/tower-map.html
@@ -8,6 +8,9 @@ html { height: 100% }
 span.is_up { font-weight: bold; color: green }
 span.is_down { font-weight: bold; color: red }
 #search_location { width: 300px }
+
+.sector_list li { list-style: none }
+.sector_list li a { width: 150px }
 </style>
 
 <div id="map_canvas"></div>
@@ -300,4 +303,16 @@ Tower #<% $tower->towernum %> | <% $tower->towername %>
 <br>
 <input type="checkbox" name="show_coverage" value="<% $tower->towernum %>">
 <% emt('Show coverage') %>
+<ul class="sector_list">
+% foreach my $sector ($tower->tower_sector) {
+%   # could be more descriptive here
+  <li><% emt($sector->sectorname) %>
+%   my @links_array;
+%   foreach my $export ($sector->part_export) {
+%     $export->export_links($sector, \@links_array); # already HTML, do not escape
+%   }
+<% join(' ', @links_array) %>
+  </li>
+% }
+</ul>
 </%def>

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/Schema.pm                           |   11 +
 FS/FS/hardware_type.pm                    |    3 +
 FS/FS/part_export/tower_towercoverage.pm  |  420 +++++++++++++++++++++++++++++
 FS/FS/tower_sector.pm                     |  104 ++++++-
 httemplate/edit/process/tower.html        |    3 +-
 httemplate/elements/tr-tower_sector.html  |   24 --
 httemplate/elements/tr-tower_sectors.html |   54 +++-
 httemplate/search/tower-map.html          |   15 ++
 8 files changed, 598 insertions(+), 36 deletions(-)
 create mode 100644 FS/FS/part_export/tower_towercoverage.pm
 delete mode 100644 httemplate/elements/tr-tower_sector.html




More information about the freeside-commits mailing list