svc_circuit, #23879, #25933, #30830
authorMark Wells <mark@freeside.biz>
Fri, 28 Nov 2014 00:12:56 +0000 (16:12 -0800)
committerMark Wells <mark@freeside.biz>
Fri, 28 Nov 2014 00:12:56 +0000 (16:12 -0800)
39 files changed:
FS/FS/AccessRight.pm
FS/FS/Mason.pm
FS/FS/Schema.pm
FS/FS/UI/Web.pm
FS/FS/addr_block.pm
FS/FS/circuit_provider.pm [new file with mode: 0644]
FS/FS/circuit_termination.pm [new file with mode: 0644]
FS/FS/circuit_type.pm [new file with mode: 0644]
FS/FS/router.pm
FS/FS/svc_circuit.pm [new file with mode: 0644]
FS/FS/svc_phone.pm
FS/MANIFEST
FS/t/circuit_provider.t [new file with mode: 0644]
FS/t/circuit_termination.t [new file with mode: 0644]
FS/t/circuit_type.t [new file with mode: 0644]
FS/t/svc_circuit.t [new file with mode: 0644]
httemplate/browse/circuit_provider.html [new file with mode: 0644]
httemplate/browse/circuit_termination.html [new file with mode: 0644]
httemplate/browse/circuit_type.html [new file with mode: 0644]
httemplate/browse/elements/browse-simple.html [new file with mode: 0644]
httemplate/docs/part_svc-table.html
httemplate/edit/circuit_provider.html [new file with mode: 0644]
httemplate/edit/circuit_termination.html [new file with mode: 0644]
httemplate/edit/circuit_type.html [new file with mode: 0644]
httemplate/edit/elements/part_svc_column.html
httemplate/edit/elements/svc_Common.html
httemplate/edit/process/circuit_provider.html [new file with mode: 0644]
httemplate/edit/process/circuit_termination.html [new file with mode: 0644]
httemplate/edit/process/circuit_type.html [new file with mode: 0644]
httemplate/edit/process/elements/svc_Common.html
httemplate/edit/process/svc_circuit.html [new file with mode: 0644]
httemplate/edit/svc_circuit.cgi [new file with mode: 0644]
httemplate/edit/svc_phone.cgi
httemplate/elements/menu.html
httemplate/elements/tr-select-svc_circuit.html [new file with mode: 0644]
httemplate/search/svc_circuit.cgi [new file with mode: 0644]
httemplate/view/elements/svc_Common.html
httemplate/view/svc_circuit.html [new file with mode: 0644]
httemplate/view/svc_phone.cgi

index 6d3937e..9e35aac 100644 (file)
@@ -310,6 +310,7 @@ tie my %rights, 'Tie::IxHash',
     'Services: Alarm services',
     'Services: External services',
     'Usage: RADIUS sessions',
+    'Services: Circuits',
     'Usage: Call Detail Records (CDRs)',
     'Usage: Unrateable CDRs',
     'Usage: Time worked',
index b9a45e9..49b2d20 100644 (file)
@@ -370,6 +370,10 @@ if ( -e $addl_handler_use_file ) {
   use FS::deploy_zone;
   use FS::deploy_zone_block;
   use FS::deploy_zone_vertex;
+  use FS::circuit_type;
+  use FS::circuit_provider;
+  use FS::circuit_termination;
+  use FS::svc_circuit;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {
index f030cde..3ff475f 100644 (file)
@@ -4001,6 +4001,7 @@ sub tables_hashref {
         'max_simultaneous',               'int', 'NULL',      '', '', '',
         'e911_class',                    'char', 'NULL',       1, '', '',
         'e911_type',                     'char', 'NULL',       1, '', '', 
+        'circuit_svcnum',                 'int', 'NULL',      '', '', '',
       ],
       'primary_key' => 'svcnum',
       'unique' => [ [ 'sms_carrierid', 'sms_account'] ],
@@ -4213,6 +4214,10 @@ sub tables_hashref {
                           { columns    => [ 'svcnum' ],
                             table      => 'svc_pbx',
                           },
+                          { columns    => [ 'circuit_svcnum' ],
+                            table      => 'svc_circuit',
+                            references => [ 'svcnum' ],
+                          },
                         ],
     },
 
@@ -4533,6 +4538,74 @@ sub tables_hashref {
       'index'  => [],
     },
 
+    'circuit_type' => {
+      'columns' => [
+        'typenum',     'serial',     '',      '', '', '',
+        'typename',   'varchar',     '', $char_d, '', '',
+        'disabled',      'char', 'NULL',       1, '', '',
+        # speed? number of voice lines? anything else?
+      ],
+      'primary_key' => 'typenum',
+      'unique' => [ [ 'typename' ] ],
+      'index'  => [],
+    },
+
+    'circuit_provider' => {
+      'columns' => [
+        'providernum', 'serial',     '',      '', '', '',
+        'provider',   'varchar',     '', $char_d, '', '',
+        'disabled',      'char', 'NULL',       1, '', '', 
+      ],
+      'primary_key' => 'providernum',
+      'unique' => [ [ 'provider' ], ],
+      'index'  => [],
+    },
+
+    'circuit_termination' => {
+      'columns' => [
+        'termnum',     'serial',     '',      '', '', '',
+        'termination','varchar',     '', $char_d, '', '',
+        'disabled',      'char', 'NULL',       1, '', '',
+      ],
+      'primary_key' => 'termnum',
+      'unique' => [ [ 'termination' ] ],
+      'index' => [],
+    },
+
+    'svc_circuit' => {
+      'columns' => [
+        'svcnum',                   'int',     '', '', '', '',
+        'typenum',                  'int',     '', '', '', '',
+        'providernum',              'int',     '', '', '', '',
+        'termnum',                  'int',     '', '', '', '',
+        'circuit_id',           'varchar',     '', 64, '', '',
+        'desired_due_date',         'int', 'NULL', '', '', '',
+        'due_date',                 'int', 'NULL', '', '', '',
+        'vendor_order_id',      'varchar', 'NULL', $char_d,  '', '',
+        'vendor_qual_id',       'varchar', 'NULL', $char_d,  '', '',
+        'vendor_order_type',    'varchar', 'NULL', $char_d,  '', '',
+        'vendor_order_status',  'varchar', 'NULL', $char_d,  '', '',
+        'endpoint_ip_addr',     'varchar', 'NULL', 40, '', '',
+        'endpoint_mac_addr',    'varchar', 'NULL', 12, '', '',
+      ],
+      'primary_key' => 'svcnum',
+      'unique'      => [],
+      'index'       => [ [ 'providernum' ], [ 'typenum' ] ],
+      'foreign_keys' => [
+                          { columns => [ 'svcnum' ],
+                            table   => 'cust_svc',
+                          },
+                          { columns => [ 'typenum' ],
+                            table   => 'circuit_type',
+                          },
+                          { columns => [ 'providernum' ],
+                            table   => 'circuit_provider',
+                          },
+                          { columns => [ 'termnum' ],
+                            table   => 'circuit_termination',
+                          },
+      ],
+    },
     %{ tables_hashref_torrus() },
 
     # tables of ours for doing torrus virtual port combining
index 0aeaa5b..483bded 100644 (file)
@@ -113,16 +113,16 @@ sub svc_url {
     if $DEBUG;
   if ( $opt{m}->interp->comp_exists("/$opt{action}/$svcdb.cgi") ) {
     $url = "$svcdb.cgi?";
+  } elsif ( $opt{m}->interp->comp_exists("/$opt{action}/$svcdb.html") ) {
+    $url = "$svcdb.html?";
   } else {
-
     my $generic = $opt{action} eq 'search' ? 'cust_svc' : 'svc_Common';
 
     $url = "$generic.html?svcdb=$svcdb;";
     $url .= 'svcnum=' if $query =~ /^\d+(;|$)/ or $query eq '';
   }
 
-  import FS::CGI 'rooturl'; #WTF!  why is this necessary
-  my $return = rooturl(). "$opt{action}/$url$query";
+  my $return = FS::CGI::rooturl(). "$opt{action}/$url$query";
 
   $return = qq!<A HREF="$return">! if $opt{ahref};
 
@@ -574,6 +574,19 @@ sub cust_aligns {
   }
 }
 
+=item cust_links
+
+Returns an array of links to view/cust_main.cgi, for use with cust_fields.
+
+=cut
+
+sub cust_links {
+  my $link = [ FS::CGI::rooturl().'view/cust_main.cgi?', 'custnum' ];
+
+  return map { $_ eq 'cust_status_label' ? '' : $link }
+    @cust_fields;
+}
+
 =item is_mobile
 
 Utility function to determine if the client is a mobile browser.
index 77ac334..7687334 100755 (executable)
@@ -412,8 +412,6 @@ sub label {
   ($router ? $router->routername : '(unallocated)'). ':'. $self->NetAddr;
 }
 
-=back
-
 =head1 BUGS
 
 Minimum block size should be a config option.  It's hardcoded at /30 right
diff --git a/FS/FS/circuit_provider.pm b/FS/FS/circuit_provider.pm
new file mode 100644 (file)
index 0000000..6cb7841
--- /dev/null
@@ -0,0 +1,101 @@
+package FS::circuit_provider;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::circuit_provider - Object methods for circuit_provider records
+
+=head1 SYNOPSIS
+
+  use FS::circuit_provider;
+
+  $record = new FS::circuit_provider \%hash;
+  $record = new FS::circuit_provider { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::circuit_provider object represents a telecom carrier that provides
+physical circuits (L<FS::svc_circuit>).  FS::circuit_provider inherits from
+FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item providernum - primary key
+
+=item provider - provider name
+
+=item disabled - disabled
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record.  To add the record to the database, see L<"insert">.
+
+=cut
+
+sub table { 'circuit_provider'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=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.
+
+=item check
+
+Checks all fields to make sure this is a valid example.  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('providernum')
+    || $self->ut_text('provider')
+    || $self->ut_flag('disabled')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/circuit_termination.pm b/FS/FS/circuit_termination.pm
new file mode 100644 (file)
index 0000000..3f0afc1
--- /dev/null
@@ -0,0 +1,98 @@
+package FS::circuit_termination;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::circuit_termination - Object methods for circuit_termination records
+
+=head1 SYNOPSIS
+
+  use FS::circuit_termination;
+
+  $record = new FS::circuit_termination \%hash;
+  $record = new FS::circuit_termination { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::circuit_termination object represents a central office circuit 
+interface type.  FS::circuit_termination inherits from FS::Record.  The 
+following fields are currently supported:
+
+=over 4
+
+=item termnum - primary key
+
+=item termination - description of the termination type
+
+=item disabled - 'Y' if this is disabled
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new example.  To add the example to the database, see L<"insert">.
+
+=cut
+
+sub table { 'circuit_termination'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=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.
+
+=item check
+
+Checks all fields to make sure this is a valid example.  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('termnum')
+    || $self->ut_text('termination')
+    || $self->ut_flag('disabled')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/circuit_type.pm b/FS/FS/circuit_type.pm
new file mode 100644 (file)
index 0000000..3b36536
--- /dev/null
@@ -0,0 +1,98 @@
+package FS::circuit_type;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::circuit_type - Object methods for circuit_type records
+
+=head1 SYNOPSIS
+
+  use FS::circuit_type;
+
+  $record = new FS::circuit_type \%hash;
+  $record = new FS::circuit_type { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::circuit_type object represents a circuit type (such as "DS1" or "OC3").
+FS::circuit_type inherits from FS::Record.  The following fields are currently
+supported:
+
+=over 4
+
+=item typenum - primary key
+
+=item typename - name of the circuit type
+
+=item disabled - 'Y' if this is disabled
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new example.  To add the example to the database, see L<"insert">.
+
+=cut
+
+sub table { 'circuit_type'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=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.
+
+=item check
+
+Checks all fields to make sure this is a valid example.  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('typenum')
+    || $self->ut_text('typename')
+    || $self->ut_flag('disabled')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
index b3efa3b..45d9f2b 100755 (executable)
@@ -202,6 +202,13 @@ sub delete {
 Returns a list of FS::addr_block objects (address blocks) associated
 with this object.
 
+=cut
+
+sub addr_block {
+  my $self = shift;
+  qsearch('addr_block', { routernum => $self->routernum });
+}
+
 =item auto_addr_block
 
 Returns a list of address blocks on which auto-assignment of IP addresses
@@ -209,11 +216,6 @@ is enabled.
 
 =cut
 
-sub addr_block {
-  my $self = shift;
-  return qsearch('addr_block', { routernum => $self->routernum });
-}
-
 sub auto_addr_block {
   my $self = shift;
   return () if $self->manual_addr;
diff --git a/FS/FS/svc_circuit.pm b/FS/FS/svc_circuit.pm
new file mode 100644 (file)
index 0000000..06015bf
--- /dev/null
@@ -0,0 +1,230 @@
+package FS::svc_circuit;
+
+use strict;
+use base qw(
+  FS::svc_IP_Mixin
+  FS::svc_MAC_Mixin
+  FS::svc_Common
+);
+use FS::Record qw( qsearch qsearchs );
+use FS::circuit_provider;
+use FS::circuit_type;
+use FS::circuit_termination;
+
+=head1 NAME
+
+FS::svc_circuit - Object methods for svc_circuit records
+
+=head1 SYNOPSIS
+
+  use FS::svc_circuit;
+
+  $record = new FS::svc_circuit \%hash;
+  $record = new FS::svc_circuit { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::svc_circuit object represents a telecom circuit service (other than 
+an analog phone line, which is svc_phone, or a DSL Internet connection, 
+which is svc_dsl).  FS::svc_circuit inherits from FS::svc_IP_Mixin,
+FS::MAC_Mixin, and FS::svc_Common.  The following fields are currently
+supported:
+
+=over 4
+
+=item svcnum - primary key; see also L<FS::cust_svc>
+
+=item typenum - circuit type (such as DS1, DS1-PRI, DS3, OC3, etc.); foreign
+key to L<FS::circuit_type>.
+
+=item providernum - circuit provider (telco); foreign key to 
+L<FS::circuit_provider>.
+
+=item termnum - circuit termination type; foreign key to 
+L<FS::circuit_termination>
+
+=item circuit_id - circuit ID string defined by the provider
+
+=item desired_due_date - the requested date for completion of the circuit
+order
+
+=item due_date - the provider's committed date for completion of the circuit
+order
+
+=item vendor_order_id - the provider's order number
+
+=item vendor_qual_id - the qualification number, if a qualification was 
+performed
+
+=item vendor_order_type -
+
+=item vendor_order_status - the order status: ACCEPTED, PENDING, COMPLETED,
+etc.
+
+=item endpoint_ip_addr - the IP address of the endpoint equipment, if any. 
+This will be validated as an IP address but not assigned from managed address
+space or checked for uniqueness.
+
+=item endpoint_mac_addr - the MAC address of the endpoint.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new circuit service.  To add the record to the database, see 
+L<"insert">.
+
+=cut
+
+sub table { 'svc_circuit'; }
+
+sub table_info {
+  my %dis = ( disable_default => 1, disable_fixed => 1,
+              disabled_inventory => 1, disable_select => 1 );
+
+  tie my %fields, 'Tie::IxHash', (
+    'svcnum'            => 'Service',
+    'providernum'       => {
+                              label         => 'Provider',
+                              type          => 'select',
+                              select_table  => 'circuit_provider',
+                              select_key    => 'providernum',
+                              select_label  => 'provider',
+                              disable_inventory => 1,
+                           },
+    'typenum'           => {
+                              label         => 'Circuit type',
+                              type          => 'select',
+                              select_table  => 'circuit_type',
+                              select_key    => 'typenum',
+                              select_label  => 'typename',
+                              disable_inventory => 1,
+                           },
+    'termnum'           => {
+                              label         => 'Termination type',
+                              type          => 'select',
+                              select_table  => 'circuit_termination',
+                              select_key    => 'termnum',
+                              select_label  => 'termination',
+                              disable_inventory => 1,
+                           },
+    'circuit_id'        => { label => 'Circuit ID', %dis },
+    'desired_due_date'  => { label => 'Desired due date',
+                             %dis
+                           },
+    'due_date'          => { label => 'Due date',
+                             %dis
+                           },
+    'vendor_order_id'   => { label => 'Vendor order ID', %dis },
+    'vendor_qual_id'    => { label => 'Vendor qualification ID', %dis },
+    'vendor_order_type' => {
+                              label => 'Vendor order type',
+                              disable_inventory => 1
+                           }, # should be a select?
+    'vendor_order_status' => {
+                              label => 'Vendor order status',
+                              disable_inventory => 1
+                             }, # should also be a select?
+    'endpoint_ip_addr'  => {
+                              label => 'Endpoint IP address',
+                           },
+    'endpoint_mac_addr' => {
+                              label => 'Endpoint MAC address',
+                              type => 'input-mac_addr',
+                              disable_inventory => 1,
+                           },
+  );
+  return {
+    'name'              => 'Circuit',
+    'name_plural'       => 'Circuits',
+    'longname_plural'   => 'Voice and data circuit services',
+    'display_weight'    => 72,
+    'cancel_weight'     => 85, # after svc_phone
+    'fields'            => \%fields,
+  };
+}
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=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.
+
+=item check
+
+Checks all fields to make sure this is a valid service.  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 $mac_addr = uc($self->get('endpoint_mac_addr'));
+  $mac_addr =~ s/[\W_]//g;
+  $self->set('endpoint_mac_addr', $mac_addr);
+
+  my $error = 
+    $self->ut_numbern('svcnum')
+    || $self->ut_number('typenum')
+    || $self->ut_number('providernum')
+    || $self->ut_text('circuit_id')
+    || $self->ut_numbern('desired_due_date')
+    || $self->ut_numbern('due_date')
+    || $self->ut_textn('vendor_order_id')
+    || $self->ut_textn('vendor_qual_id')
+    || $self->ut_textn('vendor_order_type')
+    || $self->ut_textn('vendor_order_status')
+    || $self->ut_ipn('endpoint_ip_addr')
+    || $self->ut_textn('endpoint_mac_addr')
+  ;
+
+  # no canonical values yet for vendor_order_status or _type
+
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=item label
+
+Returns the circuit ID.
+
+=cut
+
+sub label {
+  my $self = shift;
+  $self->get('circuit_id');
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
index 647412f..56104ba 100644 (file)
@@ -187,6 +187,14 @@ sub table_info {
                          select_label => 'domain',
                          disable_inventory => 1,
                        },
+        'circuit_svcnum'   => { label             => 'Circuit',
+                                type              => 'select',
+                                select_table      => 'svc_domain',
+                                select_key        => 'svcnum',
+                                select_label      => 'circuit_label',
+                                disable_inventory => 1,
+                              },
+
         'sms_carrierid'    => { label             => 'SMS Carrier',
                                 type              => 'select',
                                 select_table      => 'cdr_carrier',
@@ -711,6 +719,8 @@ sub radius_groups {
 
 =item sms_cdr_carrier
 
+Returns the L<FS::cdr_carrier> assigned as the SMS carrier for this phone.
+
 =cut
 
 sub sms_cdr_carrier {
@@ -721,6 +731,8 @@ sub sms_cdr_carrier {
 
 =item sms_carriername
 
+Returns the name of the SMS carrier, or an empty string if there isn't one.
+
 =cut
 
 sub sms_carriername {
@@ -729,6 +741,29 @@ sub sms_carriername {
   $cdr_carrier->carriername;
 }
 
+=item svc_circuit
+
+Returns the L<FS::svc_circuit> assigned as the trunk for this phone line.
+
+=item circuit_label
+
+Returns the label of the circuit (the part_svc label followed by the 
+circuit ID), or an empty string if there isn't one.
+
+=cut
+
+sub svc_circuit {
+  my $self = shift;
+  my $svcnum = $self->get('circuit_svcnum') or return '';
+  return FS::svc_circuit->by_key($svcnum);
+}
+
+sub circuit_label {
+  my $self = shift;
+  my $svc_circuit = $self->svc_circuit or return '';
+  return join(' ', $svc_circuit->part_svc->svc, $svc_circuit->circuit_id);
+}
+
 =item phone_device
 
 Returns any FS::phone_device records associated with this service.
index dc6545f..9cf3f9d 100644 (file)
@@ -776,3 +776,11 @@ t/deploy_zone_block.t
 FS/deploy_zone_vertex.pm
 t/deploy_zone_vertex.t
 
+FS/circuit_type.pm
+t/circuit_type.t
+FS/circuit_provider.pm
+t/circuit_provider.t
+FS/circuit_termination.pm
+t/circuit_termination.t
+FS/svc_circuit.pm
+t/svc_circuit.t
diff --git a/FS/t/circuit_provider.t b/FS/t/circuit_provider.t
new file mode 100644 (file)
index 0000000..753a156
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::circuit_provider;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/circuit_termination.t b/FS/t/circuit_termination.t
new file mode 100644 (file)
index 0000000..6f51271
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::circuit_termination;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/circuit_type.t b/FS/t/circuit_type.t
new file mode 100644 (file)
index 0000000..dbb6e0a
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::circuit_type;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_circuit.t b/FS/t/svc_circuit.t
new file mode 100644 (file)
index 0000000..7fefcc0
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_circuit;
+$loaded=1;
+print "ok 1\n";
diff --git a/httemplate/browse/circuit_provider.html b/httemplate/browse/circuit_provider.html
new file mode 100644 (file)
index 0000000..12f6532
--- /dev/null
@@ -0,0 +1,11 @@
+<& elements/browse-simple.html,
+  'table'               => 'circuit_provider',
+  'title'               => 'Circuit providers',
+  'menubar'             => [ 'Circuit types' => 'circuit_type.html',
+                             'Circuit terminations' => 'circuit_termination.html'
+                           ],
+  'name_singular'       => 'provider',
+  'name_header'         => 'Provider name',
+  'name_col'            => 'provider',
+  'acl'                 => 'Configuration',
+&>
diff --git a/httemplate/browse/circuit_termination.html b/httemplate/browse/circuit_termination.html
new file mode 100644 (file)
index 0000000..830ccf7
--- /dev/null
@@ -0,0 +1,11 @@
+<& elements/browse-simple.html,
+  'table'               => 'circuit_termination',
+  'title'               => 'Circuit terminations',
+  'menubar'             => [ 'Circuit types' => 'circuit_type.html',
+                             'Circuit providers' => 'circuit_provider.html'
+                           ],
+  'name_singular'       => 'termination type',
+  'name_header'         => 'Termination type',
+  'name_col'            => 'termination',
+  'acl'                 => 'Configuration',
+&>
diff --git a/httemplate/browse/circuit_type.html b/httemplate/browse/circuit_type.html
new file mode 100644 (file)
index 0000000..a145d54
--- /dev/null
@@ -0,0 +1,11 @@
+<& elements/browse-simple.html,
+  'table'               => 'circuit_type',
+  'title'               => 'Circuit types',
+  'menubar'             => [ 'Circuit providers' => 'circuit_provider.html',
+                             'Circuit terminations' => 'circuit_termination.html'
+                           ],
+  'name_singular'       => 'circuit type',
+  'name_header'         => 'Circuit type',
+  'name_col'            => 'typename',
+  'acl'                 => 'Configuration',
+&>
diff --git a/httemplate/browse/elements/browse-simple.html b/httemplate/browse/elements/browse-simple.html
new file mode 100644 (file)
index 0000000..cfa27e8
--- /dev/null
@@ -0,0 +1,57 @@
+<& browse.html,
+  'query'               => { 'table' => $table },
+  'count_query'         => "SELECT COUNT(*) FROM $table",
+  'header'              => [ '#', $opt{name_header} ],
+  'fields'              => [ $table_key, $opt{name_col} ],
+  'links'               => [ '', '' ],
+  'link_onclicks'       => [ '', $sub_edit_popup ],
+  'disableable'         => 1,
+  'disabled_statuspos'  => 2,
+  'html_init'           => $html_init,
+  %opt,
+&>
+<%doc>
+A simple wrapper around search/elements/search.html for browsing/editing
+tables that only have a primary key, a 'disabled' field, and one other column
+which is the object's name or description. Usage:
+
+<& browse-simple.html,
+  # required
+  'table'         => 'mytable',
+  'title'         => 'My Things',
+  'name_singular' => 'thing',
+  'name_col'      => 'thingname',
+  'name_header'   => 'Thing name'
+  'acl'           => 'Configure things',
+&>
+
+</%doc>
+<%init>
+my %opt = @_;
+
+my $table = delete $opt{table};
+my $name_singular = $opt{name_singular};
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right($opt{acl});
+
+my $table_key = dbdef->table($table)->primary_key;
+my $sub_edit_popup = sub {
+  my $pkey = $_[0]->get($table_key);
+  include('/elements/popup_link_onclick.html',
+    'action'      => $p."edit/$table.html?$pkey",
+    'actionlabel' => "Edit $name_singular",
+    'width'       => 350,
+    'height'      => 220,
+  );
+};
+
+my $html_init = include('/elements/popup_link.html',
+    'action'      => $p."edit/$table.html?",
+    'actionlabel' => "Add $name_singular",
+    'width'       => 350,
+    'height'      => 220,
+    'label'       => "Add a new $name_singular",
+) . '<BR>';
+
+</%init>
index 8d3711d..5e8d9e5 100644 (file)
@@ -23,6 +23,7 @@
         <LI><B>svc_broadband</B>: Wireless broadband
         <LI><B>svc_cable</B>: Cable
         <LI><B>svc_dish</B>: DISH Network
+        <LI><B>svc_circuit</B>: Phone circuits other than DSL
       </UL>
     </TD>
     <TD VALIGN="top">
diff --git a/httemplate/edit/circuit_provider.html b/httemplate/edit/circuit_provider.html
new file mode 100644 (file)
index 0000000..6c8dced
--- /dev/null
@@ -0,0 +1,21 @@
+<& elements/edit.html,
+  'popup'         => 1,
+  'table'         => 'circuit_provider',
+  'name_singular' => 'provider',
+  'labels'        => \%labels,
+  'fields'        => \@fields,
+&>
+<%init>
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @fields = (
+  'provider',
+  { field => 'disabled', type => 'checkbox', value => 'Y' }
+);
+my %labels = (
+  'providernum' => '',
+  'provider' => 'Provider name',
+  'disabled' => 'Disabled'
+);
+</%init>
diff --git a/httemplate/edit/circuit_termination.html b/httemplate/edit/circuit_termination.html
new file mode 100644 (file)
index 0000000..0317bce
--- /dev/null
@@ -0,0 +1,21 @@
+<& elements/edit.html,
+  'popup'         => 1,
+  'table'         => 'circuit_termination',
+  'name_singular' => 'termination type',
+  'labels'        => \%labels,
+  'fields'        => \@fields,
+&>
+<%init>
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @fields = (
+  'termination',
+  { field => 'disabled', type => 'checkbox', value => 'Y' }
+);
+my %labels = (
+  'termnum' => '',
+  'termination' => 'Termination type',
+  'disabled' => 'Disabled'
+);
+</%init>
diff --git a/httemplate/edit/circuit_type.html b/httemplate/edit/circuit_type.html
new file mode 100644 (file)
index 0000000..8977588
--- /dev/null
@@ -0,0 +1,21 @@
+<& elements/edit.html,
+  'popup'         => 1,
+  'table'         => 'circuit_type',
+  'name_singular' => 'circuit type',
+  'labels'        => \%labels,
+  'fields'        => \@fields,
+&>
+<%init>
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @fields = (
+  'typename',
+  { field => 'disabled', type => 'checkbox', value => 'Y' }
+);
+my %labels = (
+  'typenum' => '',
+  'typename' => 'Circuit type',
+  'disabled' => 'Disabled'
+);
+</%init>
index 64901a8..6dcb602 100644 (file)
@@ -249,7 +249,10 @@ that field.
   </TR>
 % }
 % # special case: services with attached routers (false laziness...)
-% if ( $svcdb eq 'svc_acct' or $svcdb eq 'svc_broadband' or $svcdb eq 'svc_dsl' ) {
+% if ( $svcdb eq 'svc_acct'
+%      or $svcdb eq 'svc_broadband'
+%      or $svcdb eq 'svc_dsl'
+%      or $svcdb eq 'svc_circuit' ) {
 %   push @fields, 'has_router';
   <TR>
     <TD COLSPAN=3 ALIGN="right">
index fc29327..97b630f 100644 (file)
                    my $flag = $columndef->columnflag;
 
                    if ( $flag eq 'F' ) { #fixed
-                     $f->{'type'} = length($columndef->columnvalue)
-                                      ? 'fixed'
-                                      : 'hidden';
                      $f->{'value'} = $columndef->columnvalue;
+                     if (length($columndef->columnvalue)) {
+
+                       if ( $f->{'type'} =~ /^select-?(.*)/ ) {
+                         # try to display this in a user-friendly manner
+                         if ( $f->{'table'} ) { # find matching records
+                           $f->{'value_col'} ||=
+                             dbdef->table($f->{'table'})->primary_key;
+
+                           my @values = split(',', $f->{'value'});
+                           my @recs;
+                           foreach (@values) {
+                             push @recs, qsearchs( $f->{'table'},
+                                         { $f->{'value_col'} => $_ }
+                                         );
+                           }
+                           if ( @recs ) {
+                             my $method = $f->{'name_col'};
+                             if ( $f->{'multiple'} ) {
+                               $f->{'formatted_value'} = [
+                                 map { $_->method } @recs
+                               ];
+                             } else { # there shouldn't be more than one...
+                               $f->{'formatted_value'} = $recs[0]->$method;
+                             }
+                           } # if not, then just let tr-fixed display the
+                             # values as-is
+
+                         } # other select types probably don't matter
+                       } # if it's a select
+
+                       $f->{'type'} = 'fixed';
+
+                     } else { # fixed, null
+                       $f->{'type'} = 'hidden';
+                     }
 
                    } elsif ( $flag eq 'A' ) { #auto assign from inventory
                      $f->{'type'} = 'hidden';
                                            };
 
                    } elsif ( $flag eq 'S' #selectable choice
-                               && $f->{type} !~ /^select-svc(-domain|_pbx)$/ ) {
+                               && $f->{type} !~ /^select-svc/ ) {
                      $f->{type}    = 'select';
                      $f->{options} = [ split( /\s*,\s*/,
                                                 $columndef->columnvalue)
                                      ];
-                   }
+                   } # shouldn't this be enforced for all 'S' fields?
 
-                   if (    $f->{'type'} eq 'select-svc_pbx'
-                        || $f->{'type'} eq 'select-svc-domain' 
-                      )
+                   if ( $f->{'type'} =~ /^select-svc/ )
                    {
                      $f->{'include_opt_callback'} =
                        sub { ( 'pkgnum'  => $pkgnum,
diff --git a/httemplate/edit/process/circuit_provider.html b/httemplate/edit/process/circuit_provider.html
new file mode 100644 (file)
index 0000000..0a91a17
--- /dev/null
@@ -0,0 +1,11 @@
+<& elements/process.html,
+  'table'         => 'circuit_provider',
+  'viewall_dir'   => 'browse',
+  'popup_reload'  => 'Updating',
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/circuit_termination.html b/httemplate/edit/process/circuit_termination.html
new file mode 100644 (file)
index 0000000..94d29c0
--- /dev/null
@@ -0,0 +1,11 @@
+<& elements/process.html,
+  'table'         => 'circuit_termination',
+  'viewall_dir'   => 'browse',
+  'popup_reload'  => 'Updating',
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/circuit_type.html b/httemplate/edit/process/circuit_type.html
new file mode 100644 (file)
index 0000000..58f461e
--- /dev/null
@@ -0,0 +1,11 @@
+<& elements/process.html,
+  'table'         => 'circuit_type',
+  'viewall_dir'   => 'browse',
+  'popup_reload'  => 'Updating',
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
index 55ecc5f..ca336a1 100644 (file)
@@ -29,7 +29,7 @@ my $args_callback = sub {
       map { $_ => $cgi->param("router_$_") }
       qw( routernum routername blocknum )
     });
-   if (length($router->routername) == 0) {
+   if ($router->blocknum and length($router->routername) == 0) {
       #sensible default
       $router->set('routername', $svc->label);
     }
diff --git a/httemplate/edit/process/svc_circuit.html b/httemplate/edit/process/svc_circuit.html
new file mode 100644 (file)
index 0000000..d28f913
--- /dev/null
@@ -0,0 +1,11 @@
+<& elements/svc_Common.html,
+    table       => 'svc_circuit',
+    edit_ext    => 'html',
+    redirect    => popurl(3)."view/svc_circuit.html?",
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+</%init>
diff --git a/httemplate/edit/svc_circuit.cgi b/httemplate/edit/svc_circuit.cgi
new file mode 100644 (file)
index 0000000..3f9bad5
--- /dev/null
@@ -0,0 +1,54 @@
+<& elements/svc_Common.html,
+              'table'              => 'svc_circuit',
+              'fields'             => \@fields,
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+my $conf = new FS::Conf;
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+my @fields = (
+  { field         => 'providernum',
+    type          => 'select-table',
+    table         => 'circuit_provider',
+    name_col      => 'provider',
+    disable_empty => 1,
+  },
+  { field         => 'typenum',
+    type          => 'select-table',
+    table         => 'circuit_type',
+    name_col      => 'typename',
+    disable_empty => 1,
+  },
+  { field         => 'termnum',
+    type          => 'select-table',
+    table         => 'circuit_termination',
+    name_col      => 'termination',
+    disable_empty => 1,
+  },
+  { field         => 'circuit_id',
+    size          => 40,
+  },
+  { field         => 'desired_due_date',
+    type          => 'input-date-field',
+  },
+  { field         => 'due_date',
+    type          => 'input-date-field',
+  },
+  'vendor_order_id',
+  'vendor_qual_id',
+  'vendor_order_status',
+  'endpoint_ip_addr',
+  { field         => 'endpoint_mac_addr',
+    type          => 'input-mac_addr',
+  },
+);
+
+# needed: a new_callback to migrate vendor quals over to circuits
+
+#my ($svc_new_callback, $svc_edit_callback, $svc_error_callback);
+
+</%init>
index f858205..f9c0d40 100644 (file)
@@ -2,17 +2,12 @@
      'table'            => 'svc_phone',
      'fields'           => [],
      'begin_callback'   => $begin_callback,
-     'svc_new_callback' => sub {
-       my( $cgi, $svc_x, $part_svc, $cust_pkg, $fields, $opt ) = @_;
-       $svc_x->locationnum($cust_pkg->locationnum) if $cust_pkg;
-     },
-     'svc_edit_callback' => sub {
-       my( $cgi, $svc_x, $part_svc, $cust_pkg, $fields, $opt) = @_;
-       my $conf = new FS::Conf;
-       $svc_x->sip_password('*HIDDEN*') unless $conf->exists('showpasswords');
-     },
+     'svc_new_callback'   => $svc_callback,
+     'svc_edit_callback'  => $svc_callback,
+     'svc_error_callback' => $svc_callback,
 &>
 <%init>
+my $conf = new FS::Conf;
 
 my $begin_callback = sub {
   my( $cgi, $fields, $opt ) = @_;
@@ -25,8 +20,6 @@ my $begin_callback = sub {
   die "access denied"
     unless $FS::CurrentUser::CurrentUser->access_right($right);
 
-  my $conf = new FS::Conf;
-
   push @$fields,
               'countrycode',
               { field    => 'phonenum',
@@ -149,7 +142,26 @@ my $begin_callback = sub {
 
   }
 
-};
+}; # begin_callback
 
+# svc_edit_callback / svc_new_callback
+my $svc_callback = sub {
+  my ($cgi, $svc_x, $part_svc, $cust_pkg, $fields, $opt) = @_;
 
+  push @$fields, {
+    field => 'circuit_svcnum',
+    type  => 'select-svc_circuit',
+    cust_pkg => $cust_pkg,
+    part_svc => $part_svc,
+  };
+
+  if ( $cust_pkg and not $svc_x->svcnum ) {
+    # new service, default to package location
+    $svc_x->set('locationnum', $cust_pkg->locationnum);
+  }
+
+  if ( not $conf->exists('showpasswords') and $svc_x->svcnum ) {
+    $svc_x->sip_password('*HIDDEN*');
+  }
+};
 </%init>
index 04c6490..8f87887 100644 (file)
@@ -541,6 +541,12 @@ tie my %config_alarm, 'Tie::IxHash',
   'Alarm central stations' => [ $fsurl.'browse/alarm_station.html', '' ],
 ;
 
+tie my %config_circuit, 'Tie::IxHash',
+  'Circuit types'     => [ $fsurl.'browse/circuit_type.html',         '' ],
+  'Circuit providers' => [ $fsurl.'browse/circuit_provider.html',     '' ],
+  'Termination types' => [ $fsurl.'browse/circuit_termination.html',  '' ],
+;
+
 tie my %config_export_svc, 'Tie::IxHash', ();
 if ( $curuser->access_right('Configuration') ) {
   $config_export_svc{'Service definitions'} = [ $fsurl.'browse/part_svc.cgi', 'Services are items you offer to your customers' ];
@@ -559,6 +565,8 @@ $config_export_svc{'Cable'} = [ \%config_cable, '' ]
   if $curuser->access_right('Configuration');
 $config_export_svc{'Alarm'} = [ \%config_alarm, '' ]
   if $curuser->access_right(['Alarm configuration', 'Alarm global configuration']);
+$config_export_svc{'Circuits'} = [ \%config_circuit, '' ]
+  if $curuser->access_right('Configuration');
 $config_export_svc{'Hardware types'} = [ $fsurl.'browse/hardware_class.html', 'Set up hardware type catalog' ]
   if $curuser->access_right('Configuration');
 
diff --git a/httemplate/elements/tr-select-svc_circuit.html b/httemplate/elements/tr-select-svc_circuit.html
new file mode 100644 (file)
index 0000000..fb55501
--- /dev/null
@@ -0,0 +1,41 @@
+% if ( $columnflag eq 'F' ) { # no good reason for this, but support it anyway
+  <INPUT TYPE="hidden" NAME="circuit_svcnum" VALUE="<% $circuit_svcnum %>">
+% } else { 
+  <& tr-select-table.html,
+    'table'       => 'svc_circuit',
+    'name_col'    => 'circuit_id',
+    'empty_label' => ' ',
+    %select_hash,
+    %opt
+  &>
+% } 
+<%init>
+
+my %opt = @_;
+
+my $circuit_svcnum;
+if ( $opt{'curr_value'} =~ /^(\d+)$/ ) {
+  $circuit_svcnum = $1;
+}
+
+# generally not the svcpart of the circuit service (or any circuit service)
+my $part_svc = $opt{'part_svc'}
+               || qsearchs('part_svc', { 'svcpart' => $opt{'svcpart'} });
+
+my $columnflag = $part_svc->part_svc_column('circuit_svcnum')->columnflag;
+
+my $cust_pkg = $opt{'cust_pkg'};
+my $custnum;
+$custnum = $cust_pkg->custnum if $cust_pkg;
+
+my %select_hash;
+if ( $custnum =~ /^(\d+)$/ ) {
+  %select_hash = (
+    'addl_from' => ' LEFT JOIN cust_svc USING (svcnum)' .
+                   ' LEFT JOIN cust_pkg USING (pkgnum)',
+    'extra_sql' => " WHERE cust_pkg.custnum = $custnum".
+                   " OR svcnum = $circuit_svcnum",
+  );
+}
+
+</%init>
diff --git a/httemplate/search/svc_circuit.cgi b/httemplate/search/svc_circuit.cgi
new file mode 100644 (file)
index 0000000..c14c55f
--- /dev/null
@@ -0,0 +1,65 @@
+<& elements/svc_Common.html,
+  'title'       => 'Circuit Search Results',
+  'name'        => 'circuit services',
+  'query'       => $query,
+  'count_query' => $query->{'count_query'},
+  'redirect'    => [ popurl(2). "view/svc_circuit.html?", 'svcnum' ],
+  'header'      => [ '#',
+                     'Provider',
+                     'Type',
+                     'Termination',
+                     'Circuit ID',
+                     'IP Address',
+                     FS::UI::Web::cust_header($cgi->param('cust_fields')),
+                   ],
+  'fields'      => [ 'svcnum',
+                     'provider',
+                     'typename',
+                     'termination',
+                     'circuit_id',
+                     'ip_addr',
+                     \&FS::UI::Web::cust_fields,
+                   ],
+  'links'       => [ $link,
+                     '',
+                     '',
+                     '',
+                     $link,
+                     $link,
+                     FS::UI::Web::cust_links($cgi->param('cust_fields')),
+                   ],
+  'align'       => 'rlllll'.  FS::UI::Web::cust_aligns(),
+  'color'       => [ 
+                     ('') x 6,
+                     FS::UI::Web::cust_colors(),
+                   ],
+  'style'       => [ 
+                     ('') x 6,
+                     FS::UI::Web::cust_styles(),
+                   ],
+
+&>
+<%init>
+
+die "access denied" unless
+  $FS::CurrentUser::CurrentUser->access_right('List services');
+
+my $conf = new FS::Conf;
+
+my %search_hash;
+if ( $cgi->param('magic') eq 'unlinked' ) {
+  %search_hash = ( 'unlinked' => 1 );
+} else {
+  foreach (qw( custnum agentnum svcpart cust_fields )) {
+    $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
+  }
+  foreach (qw(pkgpart routernum towernum sectornum)) {
+    $search_hash{$_} = [ $cgi->param($_) ] if $cgi->param($_);
+  }
+}
+
+my $query = FS::svc_circuit->search(\%search_hash);
+
+my $link = [ $p.'view/svc_circuit.html?', 'svcnum' ];
+
+</%init>
index 1818d34..501fea0 100644 (file)
@@ -32,6 +32,15 @@ function areyousure(href) {
     window.location.href = href;
 }
 </SCRIPT>
+<STYLE>
+  td.content {
+    background-color: #ffffff;
+  }
+  .error {
+    color: #ff0000;
+    font-weight: bold;
+  }
+</STYLE>
 
 % if ( $custnum ) { 
 
@@ -67,63 +76,20 @@ function areyousure(href) {
 
 <% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>
 
-% my @inventory_items = $svc_x->inventory_item;
 % foreach my $f ( @$fields ) {
-%
-%   my($field, $type, $value);
-%   if ( ref($f) ) {
-%     $field = $f->{'field'};
-%     $type  = $f->{'type'} || 'text';
-%     if ( $f->{'value_callback'} ) {
-%       my $hack_strict_refs = \&{ $f->{'value_callback'} };
-%       $value = &$hack_strict_refs($svc_x);
-%     } else {
-%       $value = exists($f->{'value'})
-%                  ? $f->{'value'}
-%                  : encode_entities($svc_x->$field);
-%     }
-%   } else {
-%     $field = $f;
-%     $type = 'text';
-%     $value = encode_entities($svc_x->$field);
-%   }
-%
-%   my $columndef = $part_svc->part_svc_column($field);
-%   if ( $columndef->columnflag =~ /^[MA]$/ && $columndef->columnvalue =~ /,/ )
-%   {
-%     # inventory-select field with multiple classes
-%     # show the class name to disambiguate
-%     my ($item) = grep { $_->svc_field eq $field } @inventory_items;
-%     my $class = qsearchs('inventory_class', { classnum => $item->classnum });
-%     $value .= ' <i>('. $class->classname . ')</i>' if $class;
-%   }
-%   unless ($columndef->columnflag eq 'F' && !length($columndef->columnvalue)) {
-
+%   my ($field, $label, $value) = &{ $format_field }($f);
+%   next if !$field;
       <TR>
         <TD ALIGN="right">
-          <% ( $opt{labels} && exists $opt{labels}->{$field} )
-                  ? $opt{labels}->{$field}
-                  : $field
-          %>
+          <% $label %>
         </TD>
 
-%      $value = time2str($date_format,$value)
-%         if $type eq 'date' && $value;
-%      $value = time2str("$date_format %H:%M",$value)
-%         if $type eq 'datetime' && $value;
-%      $value = $value eq 'Y' ? emt('Yes') : emt('No')
-%         if $type eq 'checkbox';
-%       $value .= ' ('. (Net::MAC::Vendor::lookup($value))->[0]. ')'
-%         if $type =~ /mac_addr$/ && $value =~ /\w/i;
-%       #eventually more options for <SELECT>, etc. fields
-
-        <TD BGCOLOR="#ffffff"><% $value %><TD>
+        <TD CLASS="content">
+          <% $value %>
+        <TD>
 
       </TR>
-
-%   }
-%
-% } 
+% }
 
 % foreach (sort { $a cmp $b } $svc_x->virtual_fields) { 
   <% $svc_x->pvf($_)->widget('HTML', 'view', $svc_x->getfield($_)) %>
@@ -195,7 +161,7 @@ my $svc_x = qsearchs({
 
 my $cust_svc = $svc_x->cust_svc;
 my ($label, $value, $svcdb, $part_svc );
-my $labels = $opt{labels}; #not -> here
+my $labels = $opt{labels} || {};
 
 if ( $cust_svc ) {
   ($label, $value, $svcdb) = $cust_svc->label;
@@ -229,7 +195,10 @@ if ($pkgnum) {
 
 # attached routers
 if ( my $router = qsearchs('router', { svcnum => $svc_x->svcnum }) ) {
-  push @$fields, qw(router_routername router_block);
+  push @$fields,
+    'router_routername',
+    'router_block';
+
   $labels->{'router_routername'} = 'Attached router';
   $labels->{'router_block'} = 'Attached address block';
   $svc_x->set('router_routername', $router->routername);
@@ -237,10 +206,100 @@ if ( my $router = qsearchs('router', { svcnum => $svc_x->svcnum }) ) {
   if ( $block ) {
     $svc_x->set('router_block', $block->cidr);
   } else {
-    $svc_x->set('router_block', '<i>(none)</i>');
+    $svc_x->set('router_block', '(none)');
   }
 }
 
+my @inventory_items = $svc_x->inventory_item;
+
+my $format_field = sub {
+  my $f = shift;
+  my($field, $type, $value);
+  if ( ref($f) ) {
+    $field = $f->{'field'};
+    $type  = $f->{'type'} || 'text';
+  } else {
+    $field = $f;
+    $type = 'text';
+  }
+
+  my $columndef = $part_svc->part_svc_column($field);
+  # skip fields that are fixed and empty
+  if ( $columndef->columnflag eq 'F'
+       and length($columndef->columnvalue) == 0 ) {
+    return;
+  }
+
+  # things that override the column value: value_callback, select
+  if ( ref($f) and $f->{'value_callback'} ) {
+
+    my $hack_strict_refs = \&{ $f->{'value_callback'} };
+    $value = &$hack_strict_refs($svc_x);
+
+  } elsif ( $type eq 'select-table' ) {
+    # imitates the /elements/select-table interface
+    $value = $svc_x->$field;
+
+    my $value_col = $f->{'value_col'} ||
+                    dbdef->table($f->{'table'})->primary_key;
+    my $name_col = $f->{'name_col'} or die 'name_col required';
+    # we don't yet support multiple-valued fields here
+    my $obj = qsearchs($f->{'table'}, { $value_col => $value });
+    if ( $obj ) {
+      $value = $obj->$name_col; # can be any method of the object
+    } else {
+      # show the raw value, but mark it as an error
+      $value = '<SPAN CLASS="error">' . $f->{'table'} . ' ' .
+                encode_entities($value) . '</SPAN>';
+    }
+
+  } else {
+    $value = encode_entities($svc_x->$field);
+  }
+
+  # inventory-select field with multiple classes
+  # show the class name to disambiguate
+  if ( $columndef->columnflag =~ /^[MA]$/ && $columndef->columnvalue =~ /,/ )
+  {
+    my ($item) = grep { $_->svc_field eq $field } @inventory_items;
+    my $class = qsearchs('inventory_class', { classnum => $item->classnum });
+    $value .= ' <i>('. $class->classname . ')</i>' if $class;
+  }
+
+  # formatting tweaks
+  if ( $type eq 'date' and $value ) {
+    $value = time2str($date_format,$value)
+  } elsif ( $type eq 'datetime' and $value ) {
+    $value = time2str("$date_format %H:%M",$value)
+  } elsif ( $type eq 'checkbox' ) {
+    $value = $value eq 'Y' ? emt('Yes') : emt('No');
+  } elsif ( $type eq 'mac_addr' and $value =~ /\w/) {
+    $value .= ' ('. (Net::MAC::Vendor::lookup($value))->[0]. ')'
+  }
+
+  # 'link' option
+  my $href;
+  if ( ref($f) and exists $f->{'link'} ) {
+    my $link = $f->{'link'};
+    if ( ref($link) eq 'CODE' ) {
+      $link = &{$link}($svc_x);
+    }
+    if ( ref($link) eq 'ARRAY' ) {
+      my ($base, $method) = @$link;
+      $href = $base . $svc_x->$method();
+    } elsif ( !ref($link) ) {
+      $href = $link;
+    }
+
+    if ( $href ) {
+      $value = qq!<A HREF="$href">$value</A>!;
+    }
+  }
+
+  my $label = $opt{labels}->{$field} || $field;
+  return ($field, $label, $value);
+};
+
 &{ $opt{'svc_callback'} }( $cgi, $svc_x, $part_svc, $cust_pkg, $fields, \%opt ) 
     if $opt{'svc_callback'};
 </%init>
diff --git a/httemplate/view/svc_circuit.html b/httemplate/view/svc_circuit.html
new file mode 100644 (file)
index 0000000..c8d5d23
--- /dev/null
@@ -0,0 +1,80 @@
+<& elements/svc_Common.html,
+  'table'        => 'svc_circuit',
+  'labels'       => \%labels,
+  'fields'       => \@fields,
+  'html_foot'    => sub { $self->call_method('.foot', @_) },
+&>
+<%method .foot>
+% my $svc_circuit = shift;
+% my $link = [ 'svc_phone.cgi?', 'svcnum' ];
+% if ( FS::svc_phone->count('circuit_svcnum = '.$svc_circuit->svcnum) ) {
+<& /search/elements/search.html,
+
+  'title' => 'Provisioned phone services',
+  'name_singular' => 'phone number',
+  'query' => { 'table'      => 'svc_phone',
+               'hashref'    => { 'circuit_svcnum' => $svc_circuit->svcnum },
+               'addl_from'  => ' LEFT JOIN cust_svc USING (svcnum)'.
+                               ' LEFT JOIN part_svc USING (svcpart)',
+               'select'     => 'svc_phone.*, part_svc.*',
+             },
+  'count_query' => 'SELECT COUNT(*) FROM svc_phone WHERE circuit_svcnum = '.
+                    $svc_circuit->svcnum,
+  'header' => [ '#', 'Service', 'Phone number', ],
+  'fields' => [ 'svcnum', 'svc', 'phonenum' ],
+  'links'  => [ $link, $link, $link ],
+  'align'  => 'rlr',
+
+  'html_form' => '<SPAN CLASS="fsinnerbox-title">Phone services</SPAN>',
+  'nohtmlheader' => 1,
+  'disable_total' => 1,
+  'disable_maxselect' => 1,
+  'really_disable_download' => 1,
+&>
+  <BR>
+% }
+</%method>
+<%init>
+
+my @fields = (
+  'circuit_id',
+  { field     => 'providernum',
+    type      => 'select-table',
+    table     => 'circuit_provider',
+    name_col  => 'provider',
+  },
+  { field     => 'typenum',
+    type      => 'select-table',
+    table     => 'circuit_type',
+    name_col  => 'typename',
+  },
+  { field     => 'termnum',
+    type      => 'select-table',
+    table     => 'circuit_termination',
+    name_col  => 'termination',
+  },
+  qw( vendor_qual_id vendor_order_id vendor_order_type vendor_order_status
+      desired_due_date due_date
+      endpoint_ip_addr
+  ),
+  { field     => 'endpoint_mac_addr', type => 'mac_addr' },
+);
+
+
+my %labels = (
+  circuit_id          => 'Circuit ID',
+  providernum         => 'Provider',
+  typenum             => 'Circuit type',
+  termnum             => 'Termination',
+  vendor_qual_id      => 'Qualification ID',
+  vendor_order_id     => 'Order ID',
+  vendor_order_type   => 'Order type',
+  vendor_order_status => 'Order status',
+  desired_due_date    => 'Desired due date',
+  due_date            => 'Due date',
+  endpoint_ip_addr    => 'Endpoint IP address',
+  endpoint_mac_addr   => 'MAC address',
+);
+
+my $self = $m->request_comp;
+</%init>
index 2a2ef24..1c0fb39 100644 (file)
@@ -1,7 +1,7 @@
 <& elements/svc_Common.html,
               'table'     => 'svc_phone',
               'fields'    => \@fields,
-                 'labels'    => \%labels,
+              'labels'    => \%labels,
               'html_foot' => $html_foot,
 &>
 <%init>
@@ -53,6 +53,11 @@ if ( $conf->exists('svc_phone-lnp') ) {
   ;
 }
 
+$labels{circuit_label} = mt('Circuit');
+push @fields, { field => 'circuit_label',
+                link => [ $p.'view/svc_circuit.html?', 'circuit_svcnum' ]
+              };
+
 my $html_foot = sub {
   my $svc_phone = shift;