From 10614457fd7db63cbcc0bf9bfeebbbb99258eaa3 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Thu, 27 Nov 2014 16:12:56 -0800 Subject: [PATCH] svc_circuit, #23879, #25933, #30830 --- FS/FS/AccessRight.pm | 1 + FS/FS/Mason.pm | 4 + FS/FS/Schema.pm | 73 +++++++ FS/FS/UI/Web.pm | 19 +- FS/FS/addr_block.pm | 2 - FS/FS/circuit_provider.pm | 101 ++++++++++ FS/FS/circuit_termination.pm | 98 ++++++++++ FS/FS/circuit_type.pm | 98 ++++++++++ FS/FS/router.pm | 12 +- FS/FS/svc_circuit.pm | 230 +++++++++++++++++++++++ FS/FS/svc_phone.pm | 35 ++++ FS/MANIFEST | 8 + FS/t/circuit_provider.t | 5 + FS/t/circuit_termination.t | 5 + FS/t/circuit_type.t | 5 + FS/t/svc_circuit.t | 5 + httemplate/browse/circuit_provider.html | 11 ++ httemplate/browse/circuit_termination.html | 11 ++ httemplate/browse/circuit_type.html | 11 ++ httemplate/browse/elements/browse-simple.html | 57 ++++++ httemplate/docs/part_svc-table.html | 1 + httemplate/edit/circuit_provider.html | 21 +++ httemplate/edit/circuit_termination.html | 21 +++ httemplate/edit/circuit_type.html | 21 +++ httemplate/edit/elements/part_svc_column.html | 5 +- httemplate/edit/elements/svc_Common.html | 46 ++++- httemplate/edit/process/circuit_provider.html | 11 ++ httemplate/edit/process/circuit_termination.html | 11 ++ httemplate/edit/process/circuit_type.html | 11 ++ httemplate/edit/process/elements/svc_Common.html | 2 +- httemplate/edit/process/svc_circuit.html | 11 ++ httemplate/edit/svc_circuit.cgi | 54 ++++++ httemplate/edit/svc_phone.cgi | 36 ++-- httemplate/elements/menu.html | 8 + httemplate/elements/tr-select-svc_circuit.html | 41 ++++ httemplate/search/svc_circuit.cgi | 65 +++++++ httemplate/view/elements/svc_Common.html | 165 ++++++++++------ httemplate/view/svc_circuit.html | 80 ++++++++ httemplate/view/svc_phone.cgi | 7 +- 39 files changed, 1322 insertions(+), 86 deletions(-) create mode 100644 FS/FS/circuit_provider.pm create mode 100644 FS/FS/circuit_termination.pm create mode 100644 FS/FS/circuit_type.pm create mode 100644 FS/FS/svc_circuit.pm create mode 100644 FS/t/circuit_provider.t create mode 100644 FS/t/circuit_termination.t create mode 100644 FS/t/circuit_type.t create mode 100644 FS/t/svc_circuit.t create mode 100644 httemplate/browse/circuit_provider.html create mode 100644 httemplate/browse/circuit_termination.html create mode 100644 httemplate/browse/circuit_type.html create mode 100644 httemplate/browse/elements/browse-simple.html create mode 100644 httemplate/edit/circuit_provider.html create mode 100644 httemplate/edit/circuit_termination.html create mode 100644 httemplate/edit/circuit_type.html create mode 100644 httemplate/edit/process/circuit_provider.html create mode 100644 httemplate/edit/process/circuit_termination.html create mode 100644 httemplate/edit/process/circuit_type.html create mode 100644 httemplate/edit/process/svc_circuit.html create mode 100644 httemplate/edit/svc_circuit.cgi create mode 100644 httemplate/elements/tr-select-svc_circuit.html create mode 100644 httemplate/search/svc_circuit.cgi create mode 100644 httemplate/view/svc_circuit.html diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index 6d3937e2f..9e35aac47 100644 --- a/FS/FS/AccessRight.pm +++ b/FS/FS/AccessRight.pm @@ -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', diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index b9a45e913..49b2d2021 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -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 ) { diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index f030cdeb4..3ff475f43 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -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 diff --git a/FS/FS/UI/Web.pm b/FS/FS/UI/Web.pm index 0aeaa5bea..483bded28 100644 --- a/FS/FS/UI/Web.pm +++ b/FS/FS/UI/Web.pm @@ -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!! 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. diff --git a/FS/FS/addr_block.pm b/FS/FS/addr_block.pm index 77ac334e9..7687334d7 100755 --- a/FS/FS/addr_block.pm +++ b/FS/FS/addr_block.pm @@ -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 index 000000000..6cb784117 --- /dev/null +++ b/FS/FS/circuit_provider.pm @@ -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::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 + +=cut + +1; + diff --git a/FS/FS/circuit_termination.pm b/FS/FS/circuit_termination.pm new file mode 100644 index 000000000..3f0afc1f9 --- /dev/null +++ b/FS/FS/circuit_termination.pm @@ -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 + +=cut + +1; + diff --git a/FS/FS/circuit_type.pm b/FS/FS/circuit_type.pm new file mode 100644 index 000000000..3b3653693 --- /dev/null +++ b/FS/FS/circuit_type.pm @@ -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 + +=cut + +1; + diff --git a/FS/FS/router.pm b/FS/FS/router.pm index b3efa3b60..45d9f2b2d 100755 --- a/FS/FS/router.pm +++ b/FS/FS/router.pm @@ -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 index 000000000..06015bf75 --- /dev/null +++ b/FS/FS/svc_circuit.pm @@ -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 + +=item typenum - circuit type (such as DS1, DS1-PRI, DS3, OC3, etc.); foreign +key to L. + +=item providernum - circuit provider (telco); foreign key to +L. + +=item termnum - circuit termination type; foreign key to +L + +=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 + +=cut + +1; + diff --git a/FS/FS/svc_phone.pm b/FS/FS/svc_phone.pm index 647412f37..56104ba61 100644 --- a/FS/FS/svc_phone.pm +++ b/FS/FS/svc_phone.pm @@ -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 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 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. diff --git a/FS/MANIFEST b/FS/MANIFEST index dc6545f6c..9cf3f9d6e 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -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 index 000000000..753a156d5 --- /dev/null +++ b/FS/t/circuit_provider.t @@ -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 index 000000000..6f5127195 --- /dev/null +++ b/FS/t/circuit_termination.t @@ -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 index 000000000..dbb6e0ac5 --- /dev/null +++ b/FS/t/circuit_type.t @@ -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 index 000000000..7fefcc04b --- /dev/null +++ b/FS/t/svc_circuit.t @@ -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 index 000000000..12f653251 --- /dev/null +++ b/httemplate/browse/circuit_provider.html @@ -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 index 000000000..830ccf7fb --- /dev/null +++ b/httemplate/browse/circuit_termination.html @@ -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 index 000000000..a145d54d9 --- /dev/null +++ b/httemplate/browse/circuit_type.html @@ -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 index 000000000..cfa27e882 --- /dev/null +++ b/httemplate/browse/elements/browse-simple.html @@ -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', +&> + + +<%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", +) . '
'; + + diff --git a/httemplate/docs/part_svc-table.html b/httemplate/docs/part_svc-table.html index 8d3711d23..5e8d9e5d1 100644 --- a/httemplate/docs/part_svc-table.html +++ b/httemplate/docs/part_svc-table.html @@ -23,6 +23,7 @@
  • svc_broadband: Wireless broadband
  • svc_cable: Cable
  • svc_dish: DISH Network +
  • svc_circuit: Phone circuits other than DSL diff --git a/httemplate/edit/circuit_provider.html b/httemplate/edit/circuit_provider.html new file mode 100644 index 000000000..6c8dcedac --- /dev/null +++ b/httemplate/edit/circuit_provider.html @@ -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' +); + diff --git a/httemplate/edit/circuit_termination.html b/httemplate/edit/circuit_termination.html new file mode 100644 index 000000000..0317bced5 --- /dev/null +++ b/httemplate/edit/circuit_termination.html @@ -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' +); + diff --git a/httemplate/edit/circuit_type.html b/httemplate/edit/circuit_type.html new file mode 100644 index 000000000..897758897 --- /dev/null +++ b/httemplate/edit/circuit_type.html @@ -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' +); + diff --git a/httemplate/edit/elements/part_svc_column.html b/httemplate/edit/elements/part_svc_column.html index 64901a80f..6dcb602fe 100644 --- a/httemplate/edit/elements/part_svc_column.html +++ b/httemplate/edit/elements/part_svc_column.html @@ -249,7 +249,10 @@ that field. % } % # 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'; diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html index fc29327ae..97b630f76 100644 --- a/httemplate/edit/elements/svc_Common.html +++ b/httemplate/edit/elements/svc_Common.html @@ -103,10 +103,42 @@ 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'; @@ -127,16 +159,14 @@ }; } 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 index 000000000..0a91a178d --- /dev/null +++ b/httemplate/edit/process/circuit_provider.html @@ -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'); + + diff --git a/httemplate/edit/process/circuit_termination.html b/httemplate/edit/process/circuit_termination.html new file mode 100644 index 000000000..94d29c05c --- /dev/null +++ b/httemplate/edit/process/circuit_termination.html @@ -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'); + + diff --git a/httemplate/edit/process/circuit_type.html b/httemplate/edit/process/circuit_type.html new file mode 100644 index 000000000..58f461e2c --- /dev/null +++ b/httemplate/edit/process/circuit_type.html @@ -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'); + + diff --git a/httemplate/edit/process/elements/svc_Common.html b/httemplate/edit/process/elements/svc_Common.html index 55ecc5f79..ca336a126 100644 --- a/httemplate/edit/process/elements/svc_Common.html +++ b/httemplate/edit/process/elements/svc_Common.html @@ -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 index 000000000..d28f91329 --- /dev/null +++ b/httemplate/edit/process/svc_circuit.html @@ -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? + + diff --git a/httemplate/edit/svc_circuit.cgi b/httemplate/edit/svc_circuit.cgi new file mode 100644 index 000000000..3f9bad5b1 --- /dev/null +++ b/httemplate/edit/svc_circuit.cgi @@ -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); + + diff --git a/httemplate/edit/svc_phone.cgi b/httemplate/edit/svc_phone.cgi index f8582057e..f9c0d4005 100644 --- a/httemplate/edit/svc_phone.cgi +++ b/httemplate/edit/svc_phone.cgi @@ -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*'); + } +}; diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index 04c64905d..8f8788731 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -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 index 000000000..fb55501c5 --- /dev/null +++ b/httemplate/elements/tr-select-svc_circuit.html @@ -0,0 +1,41 @@ +% if ( $columnflag eq 'F' ) { # no good reason for this, but support it anyway + +% } 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", + ); +} + + diff --git a/httemplate/search/svc_circuit.cgi b/httemplate/search/svc_circuit.cgi new file mode 100644 index 000000000..c14c55fdc --- /dev/null +++ b/httemplate/search/svc_circuit.cgi @@ -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' ]; + + diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html index 1818d34cf..501fea020 100644 --- a/httemplate/view/elements/svc_Common.html +++ b/httemplate/view/elements/svc_Common.html @@ -32,6 +32,15 @@ function areyousure(href) { window.location.href = href; } + % if ( $custnum ) { @@ -67,63 +76,20 @@ function areyousure(href) { <% ntable("#cccccc") %><% 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 .= ' ('. $class->classname . ')' if $class; -% } -% unless ($columndef->columnflag eq 'F' && !length($columndef->columnvalue)) { - +% my ($field, $label, $value) = &{ $format_field }($f); +% next if !$field; - <% ( $opt{labels} && exists $opt{labels}->{$field} ) - ? $opt{labels}->{$field} - : $field - %> + <% $label %> -% $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