From b65b8096089410001dfbcd35f9a56f9405b9f5f1 Mon Sep 17 00:00:00 2001 From: mark Date: Fri, 1 Apr 2011 02:52:24 +0000 Subject: [PATCH] svc_hardware and svc_dish, #11454 --- FS/FS/Mason.pm | 5 + FS/FS/Record.pm | 30 ++++ FS/FS/Schema.pm | 60 ++++++- FS/FS/cust_pkg.pm | 2 + FS/FS/cust_svc.pm | 14 ++ FS/FS/hardware_class.pm | 127 ++++++++++++++ FS/FS/hardware_status.pm | 116 +++++++++++++ FS/FS/hardware_type.pm | 131 ++++++++++++++ FS/FS/part_svc.pm | 5 +- FS/FS/part_svc_column.pm | 10 +- FS/FS/svc_dish.pm | 131 ++++++++++++++ FS/FS/svc_hardware.pm | 211 +++++++++++++++++++++++ FS/MANIFEST | 10 ++ FS/t/hardware_class.t | 5 + FS/t/hardware_status.t | 5 + FS/t/hardware_type.t | 5 + FS/t/svc_dish.t | 5 + FS/t/svc_hardware.t | 5 + httemplate/browse/hardware_class.html | 44 +++++ httemplate/browse/hardware_status.html | 24 +++ httemplate/browse/part_svc.cgi | 19 +- httemplate/edit/elements/edit.html | 4 + httemplate/edit/elements/svc_Common.html | 6 + httemplate/edit/hardware_class.html | 16 ++ httemplate/edit/hardware_status.html | 16 ++ httemplate/edit/hardware_type.html | 28 +++ httemplate/edit/part_svc.cgi | 32 +++- httemplate/edit/process/elements/process.html | 6 +- httemplate/edit/process/hardware_class.html | 11 ++ httemplate/edit/process/hardware_status.html | 11 ++ httemplate/edit/process/hardware_type.html | 11 ++ httemplate/edit/process/svc_dish.html | 10 ++ httemplate/edit/process/svc_hardware.html | 10 ++ httemplate/edit/svc_dish.cgi | 33 ++++ httemplate/edit/svc_hardware.cgi | 55 ++++++ httemplate/elements/menu.html | 5 +- httemplate/elements/select-hardware_class.html | 10 ++ httemplate/elements/select-hardware_type.html | 14 ++ httemplate/elements/tr-cust_svc.html | 78 +++++++++ httemplate/elements/tr-cust_svc_cancel.html | 24 +++ httemplate/elements/tr-select-hardware_type.html | 10 ++ httemplate/search/report_svc_hardware.html | 71 ++++++++ httemplate/search/svc_dish.cgi | 99 +++++++++++ httemplate/search/svc_hardware.cgi | 106 ++++++++++++ httemplate/view/cust_main/packages/services.html | 83 ++------- httemplate/view/elements/svc_Common.html | 5 +- httemplate/view/elements/svc_edit_link.html | 24 +++ httemplate/view/svc_acct.cgi | 6 +- httemplate/view/svc_broadband.cgi | 2 +- httemplate/view/svc_dish.cgi | 16 ++ httemplate/view/svc_hardware.cgi | 24 +++ httemplate/view/svc_phone.cgi | 2 +- 52 files changed, 1694 insertions(+), 98 deletions(-) create mode 100644 FS/FS/hardware_class.pm create mode 100644 FS/FS/hardware_status.pm create mode 100644 FS/FS/hardware_type.pm create mode 100644 FS/FS/svc_dish.pm create mode 100644 FS/FS/svc_hardware.pm create mode 100644 FS/t/hardware_class.t create mode 100644 FS/t/hardware_status.t create mode 100644 FS/t/hardware_type.t create mode 100644 FS/t/svc_dish.t create mode 100644 FS/t/svc_hardware.t create mode 100644 httemplate/browse/hardware_class.html create mode 100644 httemplate/browse/hardware_status.html create mode 100644 httemplate/edit/hardware_class.html create mode 100644 httemplate/edit/hardware_status.html create mode 100644 httemplate/edit/hardware_type.html create mode 100644 httemplate/edit/process/hardware_class.html create mode 100644 httemplate/edit/process/hardware_status.html create mode 100644 httemplate/edit/process/hardware_type.html create mode 100644 httemplate/edit/process/svc_dish.html create mode 100644 httemplate/edit/process/svc_hardware.html create mode 100644 httemplate/edit/svc_dish.cgi create mode 100644 httemplate/edit/svc_hardware.cgi create mode 100644 httemplate/elements/select-hardware_class.html create mode 100644 httemplate/elements/select-hardware_type.html create mode 100644 httemplate/elements/tr-cust_svc.html create mode 100644 httemplate/elements/tr-cust_svc_cancel.html create mode 100644 httemplate/elements/tr-select-hardware_type.html create mode 100755 httemplate/search/report_svc_hardware.html create mode 100755 httemplate/search/svc_dish.cgi create mode 100644 httemplate/search/svc_hardware.cgi create mode 100644 httemplate/view/elements/svc_edit_link.html create mode 100644 httemplate/view/svc_dish.cgi create mode 100644 httemplate/view/svc_hardware.cgi diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index ccab79339..dd1871739 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -273,6 +273,11 @@ if ( -e $addl_handler_use_file ) { use FS::torrus_srvderive; use FS::torrus_srvderive_component; use FS::areacode; + use FS::svc_dish; + use FS::svc_hardware; + use FS::hardware_class; + use FS::hardware_type; + use FS::hardware_status; # Sammath Naur if ( $FS::Mason::addl_handler_use ) { diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index fb83faa3f..7b52f508e 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -21,6 +21,7 @@ use FS::CurrentUser; use FS::Schema qw(dbdef); use FS::SearchCache; use FS::Msgcat qw(gettext); +use NetAddr::IP; # for validation #use FS::Conf; #dependency loop bs, in install_callback below instead use FS::part_virtual_field; @@ -2376,6 +2377,35 @@ sub ut_ipn { } } +=item ut_ip46 COLUMN + +Check/untaint IPv4 or IPv6 address. + +=cut + +sub ut_ip46 { + my( $self, $field ) = @_; + my $ip = NetAddr::IP->new($self->getfield($field)) + or return "Illegal (IP address) $field: ".$self->getfield($field); + $self->setfield($field, lc($ip->addr)); + return ''; +} + +=item ut_ip46n + +Check/untaint IPv6 or IPv6 address. May be null. + +=cut + +sub ut_ip46n { + my( $self, $field ) = @_; + if ( $self->getfield($field) =~ /^$/ ) { + $self->setfield($field, ''); + return ''; + } + $self->ut_ip46($field); +} + =item ut_coord COLUMN [ LOWER [ UPPER ] ] Check/untaint coordinates. diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 25eafa345..66847b6c0 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -1586,7 +1586,7 @@ sub tables_hashref { 'index' => [ [ 'svcnum' ], [ 'optionname' ] ], }, - 'part_pkg' => { + 'part_pkg' => { 'columns' => [ 'pkgpart', 'serial', '', '', '', '', 'pkg', 'varchar', '', $char_d, '', '', @@ -1745,6 +1745,7 @@ sub tables_hashref { 'svc', 'varchar', '', $char_d, '', '', 'svcdb', 'varchar', '', $char_d, '', '', 'disabled', 'char', 'NULL', 1, '', '', + 'preserve', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'svcpart', 'unique' => [], @@ -2003,6 +2004,63 @@ sub tables_hashref { 'index' => [ ['svcnum'] ], }, + 'svc_dish' => { + 'columns' => [ + 'svcnum', 'int', '', '', '', '', + 'acctnum', 'varchar', '', 16, '', '', + 'note', 'text', 'NULL', '', '', '', + ], + 'primary_key' => 'svcnum', + 'unique' => [ ], + 'index' => [ ], + }, + + 'svc_hardware' => { + 'columns' => [ + 'svcnum', 'int', '', '', '', '', + 'typenum', 'int', '', '', '', '', + 'serial', 'varchar', 'NULL', $char_d, '', '', + 'ip_addr', 'varchar', 'NULL', 40, '', '', + 'hw_addr', 'varchar', 'NULL', 12, '', '', + 'statusnum','int', 'NULL', '', '', '', + 'note', 'text', 'NULL', '', '', '', + ], + 'primary_key' => 'svcnum', + 'unique' => [ ], + 'index' => [ ], + }, + + 'hardware_class' => { + 'columns' => [ + 'classnum', 'serial', '', '', '', '', + 'classname', 'varchar', '', $char_d, '', '', + ], + 'primary_key' => 'classnum', + 'unique' => [ ], + 'index' => [ ], + }, + + 'hardware_type' => { + 'columns' => [ + 'typenum', 'serial', '', '', '', '', + 'classnum', 'int', '', '', '', '', + 'model', 'varchar', '', $char_d, '', '', + ], + 'primary_key' => 'typenum', + 'unique' => [ ], + 'index' => [ ], + }, + + 'hardware_status' => { + 'columns' => [ + 'statusnum', 'serial', '', '', '', '', + 'label' ,'varchar', '', $char_d, '', '', + ], + 'primary_key' => 'statusnum', + 'unique' => [ ], + 'index' => [ ], + }, + 'domain_record' => { 'columns' => [ 'recnum', 'serial', '', '', '', '', diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 2efd4fd87..13a3b6eb7 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -761,6 +761,8 @@ sub cancel { map { [ $_, $_->svc_x->table_info->{'cancel_weight'} ]; } qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } ) ) { + my $part_svc = $cust_svc->part_svc; + next if ( defined($part_svc) and $part_svc->preserve ); my $error = $cust_svc->cancel( %svc_cancel_opt ); if ( $error ) { diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm index 30e49c598..8cce7afb4 100644 --- a/FS/FS/cust_svc.pm +++ b/FS/FS/cust_svc.pm @@ -371,6 +371,20 @@ sub date_inserted { $self->h_date('insert'); } +=item pkg_cancel_date + +Returns the date this service's package was canceled. This normally only +exists for a service that's been preserved through cancellation with the +part_pkg.preserve flag. + +=cut + +sub pkg_cancel_date { + my $self = shift; + my $cust_pkg = $self->cust_pkg or return; + return $cust_pkg->getfield('cancel') || ''; +} + =item label Returns a list consisting of: diff --git a/FS/FS/hardware_class.pm b/FS/FS/hardware_class.pm new file mode 100644 index 000000000..073a97f88 --- /dev/null +++ b/FS/FS/hardware_class.pm @@ -0,0 +1,127 @@ +package FS::hardware_class; + +use strict; +use base qw( FS::Record ); +use FS::Record qw( qsearch qsearchs ); + +=head1 NAME + +FS::hardware_class - Object methods for hardware_class records + +=head1 SYNOPSIS + + use FS::hardware_class; + + $record = new FS::hardware_class \%hash; + $record = new FS::hardware_class { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::hardware_class object represents a class of hardware types which can +be assigned to similar services (see L). FS::hardware_class +inherits from FS::Record. The following fields are currently supported: + +=over 4 + +=item classnum - primary key + +=item classname - classname + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new record. To add the record to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'hardware_class'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid hardware class. 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('classnum') + || $self->ut_text('classname') + ; + return $error if $error; + + $self->SUPER::check; +} + +=item hardware_type + +Returns all L objects belonging to this class. + +=cut + +sub hardware_type { + my $self = shift; + return qsearch('hardware_type', { 'classnum' => $self->classnum }); +} + +=back + +=head1 SEE ALSO + +L, L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/hardware_status.pm b/FS/FS/hardware_status.pm new file mode 100644 index 000000000..4836fc5cb --- /dev/null +++ b/FS/FS/hardware_status.pm @@ -0,0 +1,116 @@ +package FS::hardware_status; + +use strict; +use base qw( FS::Record ); +use FS::Record qw( qsearch qsearchs ); + +=head1 NAME + +FS::hardware_status - Object methods for hardware_status records + +=head1 SYNOPSIS + + use FS::hardware_status; + + $record = new FS::hardware_status \%hash; + $record = new FS::hardware_status { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::hardware_status object represents an installation status for hardware +services. FS::hardware_status inherits from FS::Record. The following fields +are currently supported: + +=over 4 + +=item statusnum - primary key + +=item label - descriptive label + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new record. To add the record to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'hardware_status'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid status. 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('statusnum') + || $self->ut_text('label') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 SEE ALSO + +L, L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/hardware_type.pm b/FS/FS/hardware_type.pm new file mode 100644 index 000000000..ba19fcb21 --- /dev/null +++ b/FS/FS/hardware_type.pm @@ -0,0 +1,131 @@ +package FS::hardware_type; + +use strict; +use base qw( FS::Record ); +use FS::Record qw( qsearch qsearchs ); + +=head1 NAME + +FS::hardware_type - Object methods for hardware_type records + +=head1 SYNOPSIS + + use FS::hardware_type; + + $record = new FS::hardware_type \%hash; + $record = new FS::hardware_type { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::hardware_type object represents a device type (a model name or +number) assignable as a hardware service (L). +FS::hardware_type inherits from FS::Record. The following fields are +currently supported: + +=over 4 + +=item typenum - primary key + +=item classnum - key to an L record defining the class +to which this device type belongs. + +=item model - descriptive model name or number + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new record. To add the record to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'hardware_type'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid hardware type. 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('typenum') + || $self->ut_foreign_key('classnum', 'hardware_class', 'classnum') + || $self->ut_text('model') + ; + return $error if $error; + + $self->SUPER::check; +} + +=item hardware_class + +Returns the L associated with this device. + +=cut + +sub hardware_class { + my $self = shift; + return qsearchs('hardware_class', { 'classnum' => $self->classnum }); +} + +=back + +=head1 SEE ALSO + +L, L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm index 164bad079..e15b22590 100644 --- a/FS/FS/part_svc.pm +++ b/FS/FS/part_svc.pm @@ -53,6 +53,8 @@ L, and L, among others. =item disabled - Disabled flag, empty or `Y' +=item preserve - Preserve after cancellation, empty or 'Y' + =back =head1 METHODS @@ -381,6 +383,7 @@ sub check { || $self->ut_text('svc') || $self->ut_alpha('svcdb') || $self->ut_enum('disabled', [ '', 'Y' ] ) + || $self->ut_enum('preserve', [ '', 'Y' ] ) ; return $error if $error; @@ -758,7 +761,7 @@ sub process { map { my $f = $svcdb.'__'.$_; - if ( $param->{ $f.'_flag' } =~ /^[MA]$/ ) { + if ( $param->{ $f.'_flag' } =~ /^[MAH]$/ ) { $param->{ $f } = delete( $param->{ $f.'_classnum' } ); } if ( $param->{ $f.'_flag' } =~ /^S$/ ) { diff --git a/FS/FS/part_svc_column.pm b/FS/FS/part_svc_column.pm index f5b39c049..d467516ed 100644 --- a/FS/FS/part_svc_column.pm +++ b/FS/FS/part_svc_column.pm @@ -43,7 +43,7 @@ fields are currently supported: =item columnvalue - default or fixed value for the column -=item columnflag - null or empty (no default), `D' for default, `F' for fixed (unchangeable), `S' for selectable choice, `M' for manual selection from inventory, or `A' for automatic selection from inventory. For virtual fields, can also be 'X' for excluded. +=item columnflag - null or empty (no default), `D' for default, `F' for fixed (unchangeable), `S' for selectable choice, `M' for manual selection from inventory, `A' for automatic selection from inventory, or `H' for selection from a hardware class. For virtual fields, can also be 'X' for excluded. =back @@ -94,15 +94,19 @@ sub check { ; return $error if $error; - $self->columnflag =~ /^([DFSMAX]?)$/ + $self->columnflag =~ /^([DFSMAHX]?)$/ or return "illegal columnflag ". $self->columnflag; $self->columnflag(uc($1)); if ( $self->columnflag =~ /^[MA]$/ ) { $error = $self->ut_foreign_key( 'columnvalue', 'inventory_class', 'classnum' ); - return $error if $error; } + if ( $self->columnflag eq 'H' ) { + $error = + $self->ut_foreign_key( 'columnvalue', 'hardware_class', 'classnum' ); + } + return $error if $error; $self->SUPER::check; } diff --git a/FS/FS/svc_dish.pm b/FS/FS/svc_dish.pm new file mode 100644 index 000000000..5dac4f4d5 --- /dev/null +++ b/FS/FS/svc_dish.pm @@ -0,0 +1,131 @@ +package FS::svc_dish; + +use strict; +use base qw( FS::svc_Common ); +use FS::Record qw( qsearch qsearchs ); + +=head1 NAME + +FS::svc_dish - Object methods for svc_dish records + +=head1 SYNOPSIS + + use FS::svc_dish; + + $record = new FS::svc_dish \%hash; + $record = new FS::svc_dish { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::svc_dish object represents a Dish Network service. FS::svc_dish +inherits from FS::svc_Common. + +The following fields are currently supported: + +=over 4 + +=item svcnum - Primary key + +=item acctnum - DISH account number + +=item note - Installation notes: location on property, physical access, etc. + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new svc_dish object. + +=cut + +sub table { 'svc_dish'; } + +sub table_info { + my %opts = ( 'type' => 'text', + 'disable_select' => 1, + 'disable_inventory' => 1, + ); + { + 'name' => 'Dish service', + 'display_weight' => 58, + 'cancel_weight' => 85, + 'fields' => { + 'svcnum' => { label => 'Service' }, + 'acctnum' => { label => 'DISH account#', %opts }, + 'note' => { label => 'Installation notes', %opts }, + } + } +} + +sub label { + my $self = shift; + $self->acctnum; +} + +sub search_sql { + my($class, $string) = @_; + $class->search_sql_field('acctnum', $string); +} + +=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. + +# the replace method can be inherited from FS::Record + +=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 $x = $self->setfixed; + return $x unless ref $x; + + my $error = + $self->ut_numbern('svcnum') + || $self->ut_text('acctnum') + || $self->ut_textn('note') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 SEE ALSO + +L, L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/svc_hardware.pm b/FS/FS/svc_hardware.pm new file mode 100644 index 000000000..5b81a9c2b --- /dev/null +++ b/FS/FS/svc_hardware.pm @@ -0,0 +1,211 @@ +package FS::svc_hardware; + +use strict; +use base qw( FS::svc_Common ); +use FS::Record qw( qsearch qsearchs ); +use FS::hardware_type; +use FS::hardware_status; + +=head1 NAME + +FS::svc_hardware - Object methods for svc_hardware records + +=head1 SYNOPSIS + + use FS::svc_hardware; + + $record = new FS::svc_hardware \%hash; + $record = new FS::svc_hardware { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::svc_hardware object represents an equipment installation, such as +a wireless broadband receiver, satellite antenna, or DVR. FS::svc_hardware +inherits from FS::svc_Common. + +The following fields are currently supported: + +=over 4 + +=item svcnum - Primary key + +=item typenum - Device type number (see L) + +=item ip_addr - IP address + +=item hw_addr - Hardware address + +=item serial - Serial number + +=item statusnum - Service status (see L) + +=item note - Installation notes: location on property, physical access, etc. + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new svc_hardware object. + +=cut + +sub table { 'svc_hardware'; } + +sub table_info { + my %opts = ( 'type' => 'text', 'disable_select' => 1 ); + { + 'name' => 'Hardware', #? + 'name_plural' => 'Hardware', + 'display_weight' => 59, + 'cancel_weight' => 86, + 'fields' => { + 'svcnum' => { label => 'Service' }, + 'typenum' => { label => 'Device type', + type => 'select-hardware', + disable_select => 1, + disable_fixed => 1, + disable_default => 1, + disable_inventory => 1, + }, + 'serial' => { label => 'Serial number', %opts }, + 'hw_addr' => { label => 'Hardware address', %opts }, + 'ip_addr' => { label => 'IP address', %opts }, + 'statusnum' => { label => 'Service status', + type => 'select', + select_table => 'hardware_status', + select_key => 'statusnum', + select_label => 'label', + disable_inventory => 1, + }, + 'note' => { label => 'Installation notes', %opts }, + } + } +} + +sub search_sql { + my ($class, $string) = @_; + my @where = (); + + my $ip = NetAddr::IP->new($string); + if ( $ip ) { + push @where, $class->search_sql_field('ip_addr', $ip->addr); + } + + if ( $string =~ /^(\w+)$/ ) { + push @where, 'LOWER(svc_hardware.serial) LIKE \'%'.lc($string).'%\''; + } + + if ( $string =~ /^([0-9A-Fa-f]|\W)+$/ ) { + my $hex = uc($string); + $hex =~ s/\W//g; + push @where, 'svc_hardware.hw_addr LIKE \'%'.$hex.'%\''; + } + '(' . join(' OR ', @where) . ')'; +} + +sub label { + my $self = shift; + $self->serial || $self->hw_addr; +} + +=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. + +# the replace method can be inherited from FS::Record + +=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 $x = $self->setfixed; + return $x unless ref $x; + + my $hw_addr = $self->getfield('hw_addr'); + $hw_addr = join('', split(/\W/, $hw_addr)); + $self->setfield('hw_addr', $hw_addr); + + my $error = + $self->ut_numbern('svcnum') + || $self->ut_foreign_key('typenum', 'hardware_type', 'typenum') + || $self->ut_ip46n('ip_addr') + || $self->ut_hexn('hw_addr') + || $self->ut_alphan('serial') + || $self->ut_foreign_keyn('statusnum', 'hardware_status', 'statusnum') + || $self->ut_textn('note') + ; + return $error if $error; + + if ( !length($self->getfield('hw_addr')) + and !length($self->getfield('serial')) ) { + return 'Serial number or hardware address required'; + } + + $self->SUPER::check; +} + +=item hardware_type + +Returns the L object associated with this installation. + +=cut + +sub hardware_type { + my $self = shift; + return qsearchs('hardware_type', { 'typenum' => $self->typenum }); +} + +=item status_label + +Returns the 'label' field of the L object associated +with this installation. + +=cut + +sub status_label { + my $self = shift; + my $status = qsearchs('hardware_status', { 'statusnum' => $self->statusnum }) + or return ''; + $status->label; +} + + +=back + +=head1 SEE ALSO + +L, L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/MANIFEST b/FS/MANIFEST index fce790346..a293451a1 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -578,3 +578,13 @@ FS/areacode.pm t/areacode.t FS/areacode.pm t/areacode.t +FS/svc_dish.pm +t/svc_dish.t +FS/svc_hardware.pm +t/svc_hardware.t +FS/hardware_class.pm +t/hardware_class.t +FS/hardware_type.pm +t/hardware_type.t +FS/hardware_status.pm +t/hardware_status.t diff --git a/FS/t/hardware_class.t b/FS/t/hardware_class.t new file mode 100644 index 000000000..8c98234ab --- /dev/null +++ b/FS/t/hardware_class.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::hardware_class; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/hardware_status.t b/FS/t/hardware_status.t new file mode 100644 index 000000000..71b3077d5 --- /dev/null +++ b/FS/t/hardware_status.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::hardware_status; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/hardware_type.t b/FS/t/hardware_type.t new file mode 100644 index 000000000..072ed7ccd --- /dev/null +++ b/FS/t/hardware_type.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::hardware_type; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/svc_dish.t b/FS/t/svc_dish.t new file mode 100644 index 000000000..684837bc3 --- /dev/null +++ b/FS/t/svc_dish.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::svc_dish; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/svc_hardware.t b/FS/t/svc_hardware.t new file mode 100644 index 000000000..83ca81622 --- /dev/null +++ b/FS/t/svc_hardware.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::svc_hardware; +$loaded=1; +print "ok 1\n"; diff --git a/httemplate/browse/hardware_class.html b/httemplate/browse/hardware_class.html new file mode 100644 index 000000000..aef0fa39e --- /dev/null +++ b/httemplate/browse/hardware_class.html @@ -0,0 +1,44 @@ +<% include( 'elements/browse.html', + 'title' => 'Hardware Classes and Types', + 'name' => 'hardware classes', + 'menubar' => $menubar, + 'query' => { 'table' => 'hardware_class' }, + 'count_query' => 'SELECT COUNT(*) FROM hardware_class', + 'header' => [ '#', 'Hardware class', '', 'Device types' ], + 'fields' => [ 'classnum', + 'classname', + '', + $types_sub, + ], + 'links' => [ $class_link, + $class_link, + '', + '', + ], + ) +%> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('Configuration'); + +my $menubar = + [ 'Hardware statuses' => $p.'browse/hardware_status.html', + 'Add a hardware class' => $p.'edit/hardware_class.html', + 'Add a device type', => $p.'edit/hardware_type.html', ]; + +my $types_sub = sub { + my $hardware_class = shift; + my @rows = map { + my $type_link = $p.'edit/hardware_type.html?'.$_->typenum; + [ { 'data' => $_->model, 'link' => $type_link }, ] + } $hardware_class->hardware_type; + + \@rows; +}; + +my $class_link = [ "${p}edit/hardware_class.html?", 'classnum' ]; + + diff --git a/httemplate/browse/hardware_status.html b/httemplate/browse/hardware_status.html new file mode 100644 index 000000000..9695ed399 --- /dev/null +++ b/httemplate/browse/hardware_status.html @@ -0,0 +1,24 @@ +<% include( 'elements/browse.html', + 'title' => 'Hardware Statuses', + 'name' => 'hardware statuses', + 'menubar' => $menubar, + 'query' => { 'table' => 'hardware_status', }, + 'count_query' => 'SELECT COUNT(*) FROM hardware_status', + 'header' => [ '#', 'Status' ], + 'fields' => [ 'statusnum', 'label' ], + 'links' => [ $link, $link ], + ) +%> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('Configuration'); + +my $menubar = [ 'Hardware classes' => $p.'browse/hardware_class.html', + 'Add a status' => $p.'edit/hardware_status.html' ]; + +my $link = [ "${p}edit/hardware_status.html?", 'statusnum' ]; + + diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi index 82b1150cc..4549e44dd 100755 --- a/httemplate/browse/part_svc.cgi +++ b/httemplate/browse/part_svc.cgi @@ -169,14 +169,14 @@ function part_export_areyousure(href) { % my $value = &$formatter($part_svc->part_svc_column($field)->columnvalue); -% if ( $flag =~ /^[MA]$/ ) { -% $inventory_class{$value} -% ||= qsearchs('inventory_class', { 'classnum' => $value } ); -% - - <% $inventory_class{$value} - ? $inventory_class{$value}->classname - : "WARNING: inventory_class.classnum $value not found" %> +% if ( $flag =~ /^[MAH]$/ ) { +% my $select_table = ($flag eq 'H') ? 'hardware_class' : 'inventory_class'; +% $select_class{$value} ||= +% qsearchs($select_table, { 'classnum' => $value } ); +% + <% $select_class{$value} + ? $select_class{$value}->classname + : "WARNING: $select_table.classnum $value not found" %> % } else { <% $value %> @@ -208,6 +208,7 @@ my %flag = ( 'M' => 'Manual selected from inventory', #'A' => 'Automatically fill in from inventory', 'A' => 'Automatically filled in from inventory', + 'H' => 'Selected from hardware class', 'X' => 'Excluded', ); @@ -232,6 +233,6 @@ if ( $cgi->param('orderby') eq 'active' ) { @part_svc = sort { lc($a->svc) cmp lc($b->svc) } @part_svc; } -my %inventory_class = (); +my %select_class = (); diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index 1ed75c3ef..295ad8567 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -260,6 +260,10 @@ Example: % 'maxlength' => $f->{'maxlength'}, % 'postfix' => $f->{'postfix'}, % +% #textarea +% 'rows' => $f->{'rows'}, +% 'cols' => $f->{'cols'}, +% % #checkbox, title, fixed, hidden % #& deprecated weird value hashref used only by reason.html % 'value' => $f->{'value'}, diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html index aa7699555..0955d49c6 100644 --- a/httemplate/edit/elements/svc_Common.html +++ b/httemplate/edit/elements/svc_Common.html @@ -104,6 +104,12 @@ if $object->svcnum; $f->{'extra_sql'} .= ' ) '; $f->{'disable_empty'} = $object->svcnum ? 1 : 0, + } elsif ( $flag eq 'H' ) { + $f->{'type'} = 'select-hardware_type'; + $f->{'hashref'} = { + 'classnum'=>$columndef->columnvalue + }; + $f->{'empty_label'} = 'Select hardware type'; } if ( $f->{'type'} eq 'select-svc_pbx' diff --git a/httemplate/edit/hardware_class.html b/httemplate/edit/hardware_class.html new file mode 100644 index 000000000..8760dd86c --- /dev/null +++ b/httemplate/edit/hardware_class.html @@ -0,0 +1,16 @@ +<% include( 'elements/edit.html', + 'name' => 'Hardware Class', + 'table' => 'hardware_class', + 'labels' => { + 'classnum' => 'Class number', + 'classname' => 'Class name', + }, + 'viewall_dir' => 'browse', + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + + diff --git a/httemplate/edit/hardware_status.html b/httemplate/edit/hardware_status.html new file mode 100644 index 000000000..ee5f25db8 --- /dev/null +++ b/httemplate/edit/hardware_status.html @@ -0,0 +1,16 @@ +<% include( 'elements/edit.html', + 'name' => 'Hardware Status', + 'table' => 'hardware_status', + 'labels' => { + 'statusnum' => 'Status number', + 'label' => 'Description' , + }, + 'viewall_dir' => 'browse', + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + + diff --git a/httemplate/edit/hardware_type.html b/httemplate/edit/hardware_type.html new file mode 100644 index 000000000..09a272402 --- /dev/null +++ b/httemplate/edit/hardware_type.html @@ -0,0 +1,28 @@ +<% include( 'elements/edit.html', + 'name' => 'Device Type', + 'table' => 'hardware_type', + 'fields' => \@fields, + 'labels' => { + 'typenum' => 'Type number', + 'model' => 'Device model', + 'classnum' => 'Hardware class', + }, + 'viewall_url' => $p.'browse/hardware_class.html', + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my @fields = ( + { field => 'classnum', + type => 'select-table', + table => 'hardware_class', + disable_empty => 1, + name_col => 'classname', + }, + 'model', +); + + diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi index d608657dc..97e2d9694 100755 --- a/httemplate/edit/part_svc.cgi +++ b/httemplate/edit/part_svc.cgi @@ -9,7 +9,8 @@ Service Part #<% $part_svc->svcpart ? $part_svc->svcpart : "(NEW)" %>

Service
-Disable new orders {disabled} eq 'Y' ? ' CHECKED' : '' %>>
+{disabled} eq 'Y' ? ' CHECKED' : '' %>> Disable new orders
+{'preserve'} eq 'Y' ? ' CHECKED' : '' %>> Preserve this service on package cancellation

@@ -56,6 +57,9 @@ Disable new orders % 'A' => { 'desc' => 'Automatically fill in from inventory', % 'condition' => $inv_sub, % }, +% 'H' => { 'desc' => 'Select from hardware class', +% 'condition' => sub { $_[0]->{type} ne 'select-hardware' }, +% }, % 'X' => { 'desc' => 'Excluded', % 'condition' => % sub { ! $vfields{$_[1]}->{$_[2]} }, @@ -76,7 +80,7 @@ Disable new orders % #'form_action' => 'process/part_svc.cgi', % 'form_action' => 'part_svc.cgi', #self % 'form_text' => [ qw( svc svcpart ) ], -% 'form_checkbox' => [ 'disabled' ], +% 'form_checkbox' => [ 'disabled', 'preserve' ], % 'layer_callback' => sub { % my $layer = shift; % @@ -90,7 +94,7 @@ Disable new orders % my @part_export = % map { qsearch( 'part_export', {exporttype => $_ } ) } % keys %{FS::part_export::export_info($layer)}; -% $html .= '

'. table(). +% $html .= '

'. include('/elements/table.html') . % "Exports"; % foreach my $part_export ( @part_export ) { % $communigate++ if $part_export->exporttype =~ /^communigate/; @@ -179,7 +183,7 @@ Disable new orders % foreach my $f ( keys %flag ) { % % # need to template-ize more httemplate/edit/svc_* first -% next if $f eq 'M' and $layer !~ /^svc_(broadband|external|phone)$/; +% next if $f eq 'M' and $layer !~ /^svc_(broadband|external|phone|dish)$/; % % #here is where the SUB from above is called, to skip some choices % next if $flag{$f}->{condition} @@ -218,7 +222,8 @@ Disable new orders % " what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';". % " what.form.${layer}__${field}_classnum.style.display = 'none';". % " }". -% ' } else if ( f == "M" || f == "A" ) { //enable, inventory', +% ' } else if ( f == "M" || f == "A" || f == "H" ) { '. +% '//enable, inventory', % " what.form.${layer}__${field}.disabled = false;". % " what.form.${layer}__${field}.style.backgroundColor = '#ffffff';". % " what.form.${layer}__${field}.style.display = 'none';". @@ -238,10 +243,10 @@ Disable new orders % % my $disabled = $flag ? '' % : 'DISABLED STYLE="background-color: #dddddd"'; +% my $nodisplay = ' STYLE="display:none"'; % % if ( !$def->{type} || $def->{type} eq 'text' ) { % -% my $nodisplay = ' STYLE="display:none"'; % my $is_inv = ( $flag =~ /^[MA]$/ ); % % $html .= @@ -343,6 +348,16 @@ Disable new orders % #doesn't work#'element_etc' => $disabled, % ); % +% } elsif ( $def->{type} eq 'select-hardware' ) { +% +% $html .= qq!!; +% $html .= include('/elements/select-hardware_class.html', +% 'curr_value' => $value, +% 'element_name' => "${layer}__${field}_classnum", +% 'element_etc' => $flag ne 'H' && $nodisplay, +% 'empty_label' => 'Select hardware class', +% ); +% % } elsif ( $def->{type} eq 'disabled' ) { % % $html .= @@ -372,7 +387,8 @@ Disable new orders % % $html .= include('/elements/progress-init.html', % $layer, #form name -% [ qw(svc svcpart disabled exportnum), @fields ], +% [ qw(svc svcpart disabled preserve exportnum), +% @fields ], % 'process/part_svc.cgi', % $p.'browse/part_svc.cgi', % $layer, @@ -452,6 +468,7 @@ my $svcdb_info = '
  • svc_acct: Accounts - anything with a username (mailbox, shell, RADIUS, etc.) +
  • svc_hardware: Equipment supplied to customers
  • svc_external: Externally-tracked service
@@ -459,6 +476,7 @@ my $svcdb_info = '
  • svc_dsl: DSL
  • svc_broadband: Wireless broadband +
  • svc_dish: DISH Network
diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html index 53419cde0..107b3f298 100644 --- a/httemplate/edit/process/elements/process.html +++ b/httemplate/edit/process/elements/process.html @@ -133,9 +133,11 @@ Example: % } else { % % my $ext = $opt{'viewall_ext'} || 'html'; +% my $viewall_dir = $opt{'viewall_dir'} || 'search'; +% my $viewall_url = $opt{'viewall_url'} || ($viewall_dir . "/$table.$ext"); % -<% $cgi->redirect( popurl(3). ($opt{viewall_dir}||'search'). "/$table.$ext" ) %> -% +%#<% $cgi->redirect( popurl(3). ($opt{viewall_dir}||'search'). "/$table.$ext" ) %> +<% $cgi->redirect( popurl(3) . $viewall_url ) %> % } % %} diff --git a/httemplate/edit/process/hardware_class.html b/httemplate/edit/process/hardware_class.html new file mode 100644 index 000000000..64bc72efe --- /dev/null +++ b/httemplate/edit/process/hardware_class.html @@ -0,0 +1,11 @@ +<% include( 'elements/process.html', + 'table' => 'hardware_class', + 'viewall_dir' => 'browse', + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + + diff --git a/httemplate/edit/process/hardware_status.html b/httemplate/edit/process/hardware_status.html new file mode 100644 index 000000000..61f02e215 --- /dev/null +++ b/httemplate/edit/process/hardware_status.html @@ -0,0 +1,11 @@ +<% include( 'elements/process.html', + 'table' => 'hardware_status', + 'viewall_dir' => 'browse', + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + + diff --git a/httemplate/edit/process/hardware_type.html b/httemplate/edit/process/hardware_type.html new file mode 100644 index 000000000..52787011c --- /dev/null +++ b/httemplate/edit/process/hardware_type.html @@ -0,0 +1,11 @@ +<% include( 'elements/process.html', + 'table' => 'hardware_type', + 'viewall_url' => 'browse/hardware_class.html', + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + + diff --git a/httemplate/edit/process/svc_dish.html b/httemplate/edit/process/svc_dish.html new file mode 100644 index 000000000..6c8851e77 --- /dev/null +++ b/httemplate/edit/process/svc_dish.html @@ -0,0 +1,10 @@ +<% include( 'elements/svc_Common.html', + 'table' => 'svc_dish', + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific? + + diff --git a/httemplate/edit/process/svc_hardware.html b/httemplate/edit/process/svc_hardware.html new file mode 100644 index 000000000..5abf16c4b --- /dev/null +++ b/httemplate/edit/process/svc_hardware.html @@ -0,0 +1,10 @@ +<% include( 'elements/svc_Common.html', + 'table' => 'svc_hardware', + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific? + + diff --git a/httemplate/edit/svc_dish.cgi b/httemplate/edit/svc_dish.cgi new file mode 100644 index 000000000..77a223933 --- /dev/null +++ b/httemplate/edit/svc_dish.cgi @@ -0,0 +1,33 @@ +<% include( 'elements/svc_Common.html', + 'table' => 'svc_dish', + 'html_foot' => $html_foot, + '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 $html_foot = sub { }; + +my @fields = ( + { + field => 'acctnum', + type => 'text', + label => 'DISH Account #', + }, + { + field => 'note', + type => 'textarea', + rows => 4, + cols => 30, + label => 'Installation notes', + }, + +); + + diff --git a/httemplate/edit/svc_hardware.cgi b/httemplate/edit/svc_hardware.cgi new file mode 100644 index 000000000..e6cb22bcb --- /dev/null +++ b/httemplate/edit/svc_hardware.cgi @@ -0,0 +1,55 @@ +<% include( 'elements/svc_Common.html', + 'table' => 'svc_hardware', + 'html_foot' => $html_foot, + '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 $html_foot = sub { }; + +my @fields = ( + { + field => 'typenum', + type => 'select-hardware_type', + }, + { + field => 'serial', + type => 'text', + label => 'Device serial #', + }, + { + field => 'hw_addr', + type => 'text', + label => 'Hardware address', + }, + { + field => 'ip_addr', + type => 'text', + label => 'IP address', + }, + { + field => 'statusnum', + type => 'select-table', + table => 'hardware_status', + label => 'Service status', + name_col => 'label', + disable_empty => 1, + }, + { + field => 'note', + type => 'textarea', + rows => 4, + cols => 30, + label => 'Installation notes', + }, + +); + + diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index a70abe475..f558777b5 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -191,7 +191,7 @@ foreach my $svcdb ( FS::part_svc->svc_tables() ) { ]; } - if ( $svcdb eq 'svc_acct' || $svcdb eq 'svc_broadband' ) { + if ( $svcdb =~ /^svc_(acct|broadband|hardware)$/ ) { $report_svc{"Advanced $lcsname reports"} = [ $fsurl."search/report_$svcdb.html", '' ]; } @@ -532,6 +532,9 @@ $config_misc{'Inventory classes and inventory'} = [ $fsurl.'browse/inventory_cla || $curuser->access_right('Edit global inventory') || $curuser->access_right('Configuration'); +$config_misc{'Hardware types'} = [ $fsurl.'browse/hardware_class.html', 'Set up hardware type catalog' ] + if $curuser->access_right('Configuration'); + tie my %config_menu, 'Tie::IxHash'; if ( $curuser->access_right('Configuration' ) ) { %config_menu = ( diff --git a/httemplate/elements/select-hardware_class.html b/httemplate/elements/select-hardware_class.html new file mode 100644 index 000000000..692b3c9fd --- /dev/null +++ b/httemplate/elements/select-hardware_class.html @@ -0,0 +1,10 @@ +<% include( '/elements/select-table.html', + 'table' => 'hardware_class', + 'name_col' => 'classname', + 'hashref' => { 'disabled' => '' }, + %opt, + ) +%> +<%init> +my %opt = @_; + diff --git a/httemplate/elements/select-hardware_type.html b/httemplate/elements/select-hardware_type.html new file mode 100644 index 000000000..ae07798fc --- /dev/null +++ b/httemplate/elements/select-hardware_type.html @@ -0,0 +1,14 @@ +<% include( '/elements/select-table.html', + 'table' => 'hardware_type', + 'name_col' => 'model', + 'hashref' => $hashref, + %opt, + ) +%> +<%init> +my %opt = @_; +my $classnum = delete $opt{'classnum'}; +my $hashref = $opt{'hashref'} || {}; +$hashref->{'classnum'} = $classnum if $classnum; + + diff --git a/httemplate/elements/tr-cust_svc.html b/httemplate/elements/tr-cust_svc.html new file mode 100644 index 000000000..e792ff350 --- /dev/null +++ b/httemplate/elements/tr-cust_svc.html @@ -0,0 +1,78 @@ +<%doc> +tr-cust_svc - Short display of a customer service for use in view/cust_main. + +Formerly part of view/cust_main/packages/services.html, moved here for +cleanliness. + + + <% +FS::UI::Web::svc_link($m, $part_svc, $cust_svc) +%> + <% +FS::UI::Web::svc_label_link($m, $part_svc, $cust_svc) +%> + +% if ( $cust_svc->overlimit ) { + + + Overlimit: <% +time2str('%b %o %Y' . $opt{'cust_pkg-display_times'} ? ' %l:%M %P' : '', +$cust_svc->overlimit ) + %> + + +% } + +% # first column: recharge link + +% if ( $curuser->access_right('Recharge customer service') +% && $part_svc->svcdb eq 'svc_acct' +% && ( $svc_x->seconds ne '' +% || $svc_x->upbytes ne '' +% || $svc_x->downbytes ne '' +% || $svc_x->totalbytes ne '' +% ) +% ) { + ( <% svc_recharge_link($cust_svc)%> ) +% } + +% # second column: all other action links + +% if ( $part_svc->svcdb eq 'svc_broadband' ) { + ( <% + include('/elements/popup_link-ping.html', + 'ip' => $svc_x->ip_addr + ) %> ) +% my $manage_link = $opt{'svc_broadband-manage_link'}; +% if ( $manage_link ) { + ">Manage Device ) +% } +% } #svc_broadband +% if ( $curuser->access_right('Unprovision customer service') ) { + ( <% $svc_unprovision_link %> ) +% } +% if ( $part_svc->svcdb eq 'svc_pbx' && $opt{'maestro-status_test'} ) { + Test maestro status ) + +% } + + + +<%init> +my %opt = @_; +my $curuser = $FS::CurrentUser::CurrentUser; + +my $cust_svc = $opt{'cust_svc'}; +my $part_svc = $opt{'part_svc'} || $cust_svc->part_svc; +my $cust_pkg = $opt{'cust_pkg'} || $cust_svc->cust_pkg; +my $svc_x = $cust_svc->svc_x; + +my $svc_unprovision_link = + qq!Unprovision!; + + diff --git a/httemplate/elements/tr-cust_svc_cancel.html b/httemplate/elements/tr-cust_svc_cancel.html new file mode 100644 index 000000000..e7fa47a92 --- /dev/null +++ b/httemplate/elements/tr-cust_svc_cancel.html @@ -0,0 +1,24 @@ +<%doc> +tr-cust_svc_cancel - Short display of a canceled customer service +for use in view/cust_main. + + + <% +FS::UI::Web::svc_link($m, $part_svc, $cust_svc) +%> + <% +FS::UI::Web::svc_label_link($m, $part_svc, $cust_svc) +%> + +%# no action links, the service is canceled + +<%init> +my %opt = @_; +my $curuser = $FS::CurrentUser::CurrentUser; + +my $cust_svc = $opt{'cust_svc'}; +my $part_svc = $opt{'part_svc'} || $cust_svc->part_svc; +my $cust_pkg = $opt{'cust_pkg'} || $cust_svc->cust_pkg; +my $svc_x = $cust_svc->svc_x; + + diff --git a/httemplate/elements/tr-select-hardware_type.html b/httemplate/elements/tr-select-hardware_type.html new file mode 100644 index 000000000..c3066417b --- /dev/null +++ b/httemplate/elements/tr-select-hardware_type.html @@ -0,0 +1,10 @@ + + <% $opt{'label'} || 'Device type: ' %> + <% include('select-hardware_type.html', %opt) %> + + +<%init> + +my %opt = @_; + + diff --git a/httemplate/search/report_svc_hardware.html b/httemplate/search/report_svc_hardware.html new file mode 100755 index 000000000..4a763b0b4 --- /dev/null +++ b/httemplate/search/report_svc_hardware.html @@ -0,0 +1,71 @@ +<% include('/elements/header.html', $title ) %> + +
+ + + + + + + + + <% include('/elements/tr-input-text.html', + 'field' => 'serial', + 'label' => 'Serial #', + ) %> + <% include('/elements/tr-input-text.html', + 'field' => 'hw_addr', + 'label' => 'Hardware address', + ) %> + <% include('/elements/tr-input-text.html', + 'field' => 'ip_addr', + 'label' => 'IP address', + ) %> + <% include('/elements/tr-select-table.html', + 'field' => 'statusnum', + 'label' => 'Service status', + 'table' => 'hardware_status', + 'name_col' => 'label', + 'empty_label' => 'any', + ) %> + +
Search options
+ <% include('/elements/selectlayers.html', + 'field' => 'classnum', + 'label' => '', + 'options' => \@classnums, + 'labels' => \%class_labels, + 'layer_callback' => \&layer_callback, + 'html_between' => '', + ) %> +
+ +
+ + +
+ +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List packages'); #? + +my $title = 'Hardware Service Report'; + +my @classes = qsearch('hardware_class', {}); +my @classnums = ('', map { $_->classnum } @classes); +my %class_labels = ('' => 'Select hardware class', + map { $_->classnum => $_->classname } @classes); + +sub layer_callback { + my $classnum = shift or return ''; + include('/elements/select-hardware_type.html', + 'field' => 'classnum'.$classnum.'typenum', + 'classnum' => $classnum, + 'empty_label' => 'any', + ); +} + + + diff --git a/httemplate/search/svc_dish.cgi b/httemplate/search/svc_dish.cgi new file mode 100755 index 000000000..94da03537 --- /dev/null +++ b/httemplate/search/svc_dish.cgi @@ -0,0 +1,99 @@ +<% include( 'elements/search.html', + 'title' => 'Dish Network Search Results', + 'name' => 'services', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + 'Account #', + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + 'svc', + 'acctnum', + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + $link, + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rll'. FS::UI::Web::cust_aligns(), + 'color' => [ + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List services'); + +#my $conf = new FS::Conf; + +my $orderby = 'ORDER BY svcnum'; +my @extra_sql = (); +if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { + + push @extra_sql, 'pkgnum IS NULL' + if $cgi->param('magic') eq 'unlinked'; + + if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { + my $sortby = $1; + $orderby = "ORDER BY $sortby"; + } +} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { + push @extra_sql, "svcpart = $1"; +} + +my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null_right' => 'View/link unlinked services' + ); + +my $extra_sql = + scalar(@extra_sql) + ? ' WHERE '. join(' AND ', @extra_sql ) + : ''; + + +my $count_query = "SELECT COUNT(*) FROM svc_dish $addl_from $extra_sql"; +my $sql_query = { + 'table' => 'svc_dish', + 'hashref' => {}, + 'select' => join(', ', + 'svc_dish.*', + 'part_svc.svc', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => $extra_sql, + 'order_by' => $orderby, + 'addl_from' => $addl_from, +}; + +my $link = [ "${p}view/svc_dish.cgi?", 'svcnum', ]; + +my $link_cust = sub { + my $svc_x = shift; + $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : ''; +}; + + diff --git a/httemplate/search/svc_hardware.cgi b/httemplate/search/svc_hardware.cgi new file mode 100644 index 000000000..ffbb9f3b6 --- /dev/null +++ b/httemplate/search/svc_hardware.cgi @@ -0,0 +1,106 @@ +<% include('elements/search.html', + 'title' => 'Hardware service search results', + 'name' => 'installations', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link_svc, + 'header' => [ '#', + 'Service', + 'Device type', + 'Serial #', + 'Hardware addr.', + 'IP addr.', + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + 'svc', + 'model', + 'serial', + 'hw_addr', + 'ip_addr', + \&FS::UI::Web::cust_fields, + ], + 'links' => [ ($link_svc) x 6, + ( map { $_ ne 'Cust. Status' ? + $link_cust : '' } + FS::UI::Web::cust_header() ) + ], + 'align' => 'rllll' . FS::UI::Web::cust_aligns(), + 'color' => [ ('') x 4, FS::UI::Web::cust_colors() ], + 'style' => [ ('') x 4, FS::UI::Web::cust_styles() ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List services'); + + +my $addl_from = ' + LEFT JOIN cust_svc USING ( svcnum ) + LEFT JOIN part_svc USING ( svcpart ) + LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN cust_main USING ( custnum ) + LEFT JOIN hardware_type USING ( typenum )'; + +my @extra_sql; +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null_right' => 'View/link unlinked services' + ); + +if ( $cgi->param('magic') =~ /^(unlinked)$/ ) { + push @extra_sql, 'pkgnum IS NULL'; +} + +if ( lc($cgi->param('serial')) =~ /^(\w+)$/ ) { + push @extra_sql, "LOWER(serial) LIKE '%$1%'"; +} + +if ( $cgi->param('hw_addr') =~ /^(\S+)$/ ) { + my $hw_addr = uc($1); + $hw_addr =~ s/\W//g; + push @extra_sql, "hw_addr LIKE '%$hw_addr%'"; +} + +my $ip = NetAddr::IP->new($cgi->param('ip_addr')); +if ( $ip ) { + push @extra_sql, "ip_addr = '".lc($ip->addr)."'"; +} + +if ( $cgi->param('statusnum') =~ /^(\d+)$/ ) { + push @extra_sql, "statusnum = $1"; +} + +if ( $cgi->param('classnum') =~ /^(\d+)$/ ) { + push @extra_sql, "hardware_type.classnum = $1"; + if ( $cgi->param('classnum'.$1.'typenum') =~ /^(\d+)$/ ) { + push @extra_sql, "svc_hardware.typenum = $1"; + } +} + +my ($orderby) = $cgi->param('orderby') =~ /^(\w+( ASC| DESC)?)$/i; +$orderby ||= 'svcnum'; + +my $extra_sql = ''; +$extra_sql = ' WHERE '.join(' AND ', @extra_sql) if @extra_sql; + +my $sql_query = { + 'table' => 'svc_hardware', + 'select' => join(', ', + 'svc_hardware.*', + 'part_svc.svc', + 'cust_main.custnum', + 'hardware_type.model', + FS::UI::Web::cust_sql_fields(), + ), + 'hashref' => {}, + 'extra_sql' => $extra_sql, + 'order_by' => "ORDER BY $orderby", + 'addl_from' => $addl_from, +}; + +my $count_query = "SELECT COUNT(*) FROM svc_hardware $addl_from $extra_sql"; +my $link_svc = [ $p.'view/svc_hardware.cgi?', 'svcnum' ]; +my $link_cust = [ $p.'view/cust_main.cgi?', 'custnum' ]; + + diff --git a/httemplate/view/cust_main/packages/services.html b/httemplate/view/cust_main/packages/services.html index 512efccc4..1e636ad4e 100644 --- a/httemplate/view/cust_main/packages/services.html +++ b/httemplate/view/cust_main/packages/services.html @@ -10,7 +10,6 @@ function clearhint_search_cust_svc(obj, str) { } -% #foreach my $svcpart (sort {$a->{svcpart} <=> $b->{svcpart}} @{$pkg->{svcparts}}) { % foreach my $part_svc ( $cust_pkg->part_svc ) { % if ( $opt{'cust_pkg-large_pkg_size'} > 0 and @@ -36,66 +35,26 @@ function clearhint_search_cust_svc(obj, str) { % } -% else { +% else { # don't summarize % foreach my $cust_svc ( @{ $part_svc->cust_pkg_svc } ) { - - - <% FS::UI::Web::svc_link($m, $part_svc, $cust_svc) %> - <% FS::UI::Web::svc_label_link($m, $part_svc, $cust_svc) %> - <% FS::UI::Web::svc_export_links($m, $part_svc, $cust_svc) %> - - - - - - <% $cust_svc->overlimit ? "Overlimit: ". time2str('%b %o %Y' . ($opt{'cust_pkg-display_times'} ? ' %l:%M %P' : ''), $cust_svc->overlimit) : '' %> - - - - - - -% if ( $curuser->access_right('Recharge customer service') -% && $part_svc->svcdb eq 'svc_acct' -% && ( $cust_svc->svc_x->seconds ne '' -% || $cust_svc->svc_x->upbytes ne '' -% || $cust_svc->svc_x->downbytes ne '' -% || $cust_svc->svc_x->totalbytes ne '' -% ) -% ) { - ( <%svc_recharge_link($cust_svc)%> ) -% } - - - - -% my $ip_addr = $cust_svc->svc_x->ip_addr; - -% if ( $part_svc->svcdb eq 'svc_broadband' ) { - ( <% include('/elements/popup_link-ping.html', 'ip'=> $ip_addr ) %> ) - -% } - -% my $manage_link = $opt{'svc_broadband-manage_link'}; -% if ( $manage_link && $part_svc->svcdb eq 'svc_broadband' ) { -% my $svc_manage_link = eval(qq("$manage_link")); - Manage Device ) - -% } - -% if ( $curuser->access_right('Unprovision customer service') ) { - ( <%svc_unprovision_link($cust_svc)%> ) -% } - -% if ( $part_svc->svcdb eq 'svc_pbx' && $opt{'maestro-status_test'} ){ - Test maestro status ) -% } - - - +% if ( $cust_pkg->getfield('cancel') > 0 ) { + <% include('/elements/tr-cust_svc_cancel.html', + %opt, + 'part_svc' => $part_svc, + 'cust_svc' => $cust_svc, + 'cust_pkg' => $cust_pkg, + ) %> +% } +% else { + <% include('/elements/tr-cust_svc.html', + %opt, + 'part_svc' => $part_svc, + 'cust_svc' => $cust_svc, + 'cust_pkg' => $cust_pkg, + ) %> +% } #if cancel > 0 % } #foreach $cust_svc -% } - +% } #if summarizing % if ( ! $cust_pkg->get('cancel') % && $curuser->access_right('Provision customer service') % && $part_svc->num_avail @@ -160,12 +119,6 @@ sub svc_provision_link { $link; } -sub svc_unprovision_link { - my $cust_svc = shift or return ''; - qq!Unprovision!; -} - my %hints = ( svc_acct => '(user or email)', svc_domain => '(domain)', diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html index de49b50d2..7a7539da2 100644 --- a/httemplate/view/elements/svc_Common.html +++ b/httemplate/view/elements/svc_Common.html @@ -51,10 +51,7 @@ function areyousure(href) { Service #<% $svcnum %> % my $url = $opt{'edit_url'} || $p. 'edit/'. $opt{'table'}. '.cgi?'; -| Edit this <% $label %> - -| -Unprovision this Service +| <% include('/view/elements/svc_edit_link.html', 'svc' => $svc_x) %>
<% ntable("#cccccc") %><% ntable("#cccccc",2) %> diff --git a/httemplate/view/elements/svc_edit_link.html b/httemplate/view/elements/svc_edit_link.html new file mode 100644 index 000000000..a85d38077 --- /dev/null +++ b/httemplate/view/elements/svc_edit_link.html @@ -0,0 +1,24 @@ +% if ( $cancel_date ) { +Canceled <% time2str('%b %o %Y', $cancel_date) %> +% } else { + +Edit this <% $label %> | + +Unprovision this Service +% } +<%init> +my %opt = @_; +my $svc_x = $opt{'svc'} or die "'svc' required"; +my $svcdb = $opt{'table'} || $svc_x->table; +my $edit_url = $opt{'edit_url'} || + $p . 'edit/' . $svcdb . '.cgi?' . $svc_x->svcnum; +my $cancel_url = $p . 'misc/unprovision.cgi?' . $svc_x->svcnum; +my $cust_svc = $svc_x->cust_svc; # always exists +my $cancel_date = $cust_svc->pkg_cancel_date; +my ($label) = $cust_svc->label; + diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi index 4e82569fc..291298d1e 100755 --- a/httemplate/view/svc_acct.cgi +++ b/httemplate/view/svc_acct.cgi @@ -38,8 +38,8 @@ %> Service #<% $svcnum %> -| Edit this service - +| +<% include('/view/elements/svc_edit_link.html', 'svc' => $svc_acct) %> <% include( 'svc_acct/change_svc.html', 'part_svc' => \@part_svc, %gopt, @@ -105,7 +105,7 @@ my $svc_acct = qsearchs({ die "Unknown svcnum" unless $svc_acct; #false laziness w/all svc_*.cgi -my $cust_svc = qsearchs( 'cust_svc' , { 'svcnum' => $svcnum } ); +my $cust_svc = $svc_acct->cust_svc; my $pkgnum = $cust_svc->getfield('pkgnum'); my($cust_pkg, $custnum); if ($pkgnum) { diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi index 12e5f0f5d..2c44293ec 100644 --- a/httemplate/view/svc_broadband.cgi +++ b/httemplate/view/svc_broadband.cgi @@ -10,7 +10,7 @@ <% include('/elements/init_overlib.html') %> -Edit this information +<% include('/view/elements/svc_edit_link.html', $svc_acct) %>
<%ntable("#cccccc")%> diff --git a/httemplate/view/svc_dish.cgi b/httemplate/view/svc_dish.cgi new file mode 100644 index 000000000..d4aa8bfdf --- /dev/null +++ b/httemplate/view/svc_dish.cgi @@ -0,0 +1,16 @@ +<% include('elements/svc_Common.html', + 'table' => 'svc_dish', + 'labels' => \%labels, + 'fields' => \@fields, + ) +%> +<%init> + +my $fields = FS::svc_dish->table_info->{'fields'}; +my %labels = map { $_ => ( ref($fields->{$_}) + ? $fields->{$_}{'label'} + : $fields->{$_} + ); + } keys %$fields; +my @fields = qw( acctnum note ); + diff --git a/httemplate/view/svc_hardware.cgi b/httemplate/view/svc_hardware.cgi new file mode 100644 index 000000000..9cea341d7 --- /dev/null +++ b/httemplate/view/svc_hardware.cgi @@ -0,0 +1,24 @@ +<% include('elements/svc_Common.html', + 'table' => 'svc_hardware', + 'labels' => \%labels, + 'fields' => \@fields, + ) +%> +<%init> + +my $fields = FS::svc_hardware->table_info->{'fields'}; +my %labels = map { $_ => ( ref($fields->{$_}) + ? $fields->{$_}{'label'} + : $fields->{$_} + ); + } keys %$fields; +my $model = { field => 'typenum', + type => 'text', + value => sub { $_[0]->hardware_type->model } + }; +my $status = { field => 'statusnum', + type => 'text', + value => sub { $_[0]->status_label } + }; +my @fields = ($model, qw( serial hw_addr ip_addr ), $status, 'note' ); + diff --git a/httemplate/view/svc_phone.cgi b/httemplate/view/svc_phone.cgi index e4dc335ca..4a850c21c 100644 --- a/httemplate/view/svc_phone.cgi +++ b/httemplate/view/svc_phone.cgi @@ -137,7 +137,7 @@ my $html_foot = sub { #src & charged party as per voip_cdr.pm my $search; my $cust_pkg = $svc_phone->cust_svc->cust_pkg; - if ( $cust_pkg && $cust_pkg->part_pkg->option('disable_src') ) { + if ( $cust_pkg && $cust_pkg->part_pkg->option('disable_src',1) ) { $search = "charged_party=$number"; } else { $search = "charged_party_or_src=$number"; -- 2.11.0