summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FS/FS/Mason.pm5
-rw-r--r--FS/FS/Record.pm30
-rw-r--r--FS/FS/Schema.pm60
-rw-r--r--FS/FS/cust_pkg.pm2
-rw-r--r--FS/FS/cust_svc.pm14
-rw-r--r--FS/FS/hardware_class.pm127
-rw-r--r--FS/FS/hardware_status.pm116
-rw-r--r--FS/FS/hardware_type.pm131
-rw-r--r--FS/FS/part_svc.pm5
-rw-r--r--FS/FS/part_svc_column.pm10
-rw-r--r--FS/FS/svc_dish.pm131
-rw-r--r--FS/FS/svc_hardware.pm211
-rw-r--r--FS/MANIFEST10
-rw-r--r--FS/t/hardware_class.t5
-rw-r--r--FS/t/hardware_status.t5
-rw-r--r--FS/t/hardware_type.t5
-rw-r--r--FS/t/svc_dish.t5
-rw-r--r--FS/t/svc_hardware.t5
-rw-r--r--httemplate/browse/hardware_class.html44
-rw-r--r--httemplate/browse/hardware_status.html24
-rwxr-xr-xhttemplate/browse/part_svc.cgi19
-rw-r--r--httemplate/edit/elements/edit.html4
-rw-r--r--httemplate/edit/elements/svc_Common.html6
-rw-r--r--httemplate/edit/hardware_class.html16
-rw-r--r--httemplate/edit/hardware_status.html16
-rw-r--r--httemplate/edit/hardware_type.html28
-rwxr-xr-xhttemplate/edit/part_svc.cgi32
-rw-r--r--httemplate/edit/process/elements/process.html6
-rw-r--r--httemplate/edit/process/hardware_class.html11
-rw-r--r--httemplate/edit/process/hardware_status.html11
-rw-r--r--httemplate/edit/process/hardware_type.html11
-rw-r--r--httemplate/edit/process/svc_dish.html10
-rw-r--r--httemplate/edit/process/svc_hardware.html10
-rw-r--r--httemplate/edit/svc_dish.cgi33
-rw-r--r--httemplate/edit/svc_hardware.cgi55
-rw-r--r--httemplate/elements/menu.html5
-rw-r--r--httemplate/elements/select-hardware_class.html10
-rw-r--r--httemplate/elements/select-hardware_type.html14
-rw-r--r--httemplate/elements/tr-cust_svc.html78
-rw-r--r--httemplate/elements/tr-cust_svc_cancel.html24
-rw-r--r--httemplate/elements/tr-select-hardware_type.html10
-rwxr-xr-xhttemplate/search/report_svc_hardware.html71
-rwxr-xr-xhttemplate/search/svc_dish.cgi99
-rw-r--r--httemplate/search/svc_hardware.cgi106
-rw-r--r--httemplate/view/cust_main/packages/services.html83
-rw-r--r--httemplate/view/elements/svc_Common.html5
-rw-r--r--httemplate/view/elements/svc_edit_link.html24
-rwxr-xr-xhttemplate/view/svc_acct.cgi6
-rw-r--r--httemplate/view/svc_broadband.cgi2
-rw-r--r--httemplate/view/svc_dish.cgi16
-rw-r--r--httemplate/view/svc_hardware.cgi24
-rw-r--r--httemplate/view/svc_phone.cgi2
52 files changed, 1694 insertions, 98 deletions
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index ccab793..dd18717 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 fb83faa..7b52f50 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 25eafa3..66847b6 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 2efd4fd..13a3b6e 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 30e49c5..8cce7af 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 0000000..073a97f
--- /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::svc_hardware>). 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<hash> 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<FS::hardware_type> objects belonging to this class.
+
+=cut
+
+sub hardware_type {
+ my $self = shift;
+ return qsearch('hardware_type', { 'classnum' => $self->classnum });
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::hardware_type>, L<FS::Record>, 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 0000000..4836fc5
--- /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<hash> 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<FS::svc_hardware>, L<FS::Record>, 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 0000000..ba19fcb
--- /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::svc_hardware)>).
+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<FS::hardware_class> 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<hash> 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<FS::hardware_class> associated with this device.
+
+=cut
+
+sub hardware_class {
+ my $self = shift;
+ return qsearchs('hardware_class', { 'classnum' => $self->classnum });
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::svc_hardware>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm
index 164bad0..e15b225 100644
--- a/FS/FS/part_svc.pm
+++ b/FS/FS/part_svc.pm
@@ -53,6 +53,8 @@ L<FS::svc_domain>, and L<FS::svc_forward>, 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 f5b39c0..d467516 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 0000000..5dac4f4
--- /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<FS::Record>, L<FS::svc_Common>, 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 0000000..5b81a9c
--- /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<FS::hardware_type>)
+
+=item ip_addr - IP address
+
+=item hw_addr - Hardware address
+
+=item serial - Serial number
+
+=item statusnum - Service status (see L<FS::hardware_status>)
+
+=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<FS::hardware_type> 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<FS::hardware_status> 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<FS::Record>, L<FS::svc_Common>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/MANIFEST b/FS/MANIFEST
index fce7903..a293451 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 0000000..8c98234
--- /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 0000000..71b3077
--- /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 0000000..072ed7c
--- /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 0000000..684837b
--- /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 0000000..83ca816
--- /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 0000000..aef0fa3
--- /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' ];
+
+</%init>
diff --git a/httemplate/browse/hardware_status.html b/httemplate/browse/hardware_status.html
new file mode 100644
index 0000000..9695ed3
--- /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' ];
+
+</%init>
diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi
index 82b1150..4549e44 100755
--- a/httemplate/browse/part_svc.cgi
+++ b/httemplate/browse/part_svc.cgi
@@ -169,14 +169,14 @@ function part_export_areyousure(href) {
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
% 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 = ();
</%init>
diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html
index 1ed75c3..295ad85 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 aa76995..0955d49 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 0000000..8760dd8
--- /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');
+
+</%init>
diff --git a/httemplate/edit/hardware_status.html b/httemplate/edit/hardware_status.html
new file mode 100644
index 0000000..ee5f25d
--- /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');
+
+</%init>
diff --git a/httemplate/edit/hardware_type.html b/httemplate/edit/hardware_type.html
new file mode 100644
index 0000000..09a2724
--- /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',
+);
+
+</%init>
diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi
index d608657..97e2d96 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)" %>
<BR><BR>
Service <INPUT TYPE="text" NAME="svc" VALUE="<% $hashref->{svc} %>"><BR>
-Disable new orders <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>><BR>
+<INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>>&nbsp;Disable new orders<BR>
+<INPUT TYPE="checkbox" NAME="preserve" VALUE="Y"<% $hashref->{'preserve'} eq 'Y' ? ' CHECKED' : '' %>>&nbsp;Preserve this service on package cancellation<BR>
<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $hashref->{svcpart} %>">
<BR>
@@ -56,6 +57,9 @@ Disable new orders <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->
% '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 <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->
% #'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 <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->
% my @part_export =
% map { qsearch( 'part_export', {exporttype => $_ } ) }
% keys %{FS::part_export::export_info($layer)};
-% $html .= '<BR><BR>'. table().
+% $html .= '<BR><BR>'. include('/elements/table.html') .
% "<TR><TH COLSPAN=$columns>Exports</TH></TR><TR>";
% foreach my $part_export ( @part_export ) {
% $communigate++ if $part_export->exporttype =~ /^communigate/;
@@ -179,7 +183,7 @@ Disable new orders <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->
% 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 <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->
% " 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 <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->
%
% 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 <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->
% #doesn't work#'element_etc' => $disabled,
% );
%
+% } elsif ( $def->{type} eq 'select-hardware' ) {
+%
+% $html .= qq!<INPUT TYPE="text" NAME="${layer}__${field}" $disabled>!;
+% $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 <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->
%
% $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 = '
<TD VALIGN="top">
<UL STYLE="margin:0">
<LI><B>svc_acct</B>: Accounts - anything with a username (mailbox, shell, RADIUS, etc.)
+ <LI><B>svc_hardware</B>: Equipment supplied to customers
<LI><B>svc_external</B>: Externally-tracked service
</UL>
</TD>
@@ -459,6 +476,7 @@ my $svcdb_info = '
<UL STYLE="margin:0">
<LI><B>svc_dsl</B>: DSL
<LI><B>svc_broadband</B>: Wireless broadband
+ <LI><B>svc_dish</B>: DISH Network
</UL>
</TD>
<TD VALIGN="top">
diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html
index 53419cd..107b3f2 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 0000000..64bc72e
--- /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');
+
+</%init>
diff --git a/httemplate/edit/process/hardware_status.html b/httemplate/edit/process/hardware_status.html
new file mode 100644
index 0000000..61f02e2
--- /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');
+
+</%init>
diff --git a/httemplate/edit/process/hardware_type.html b/httemplate/edit/process/hardware_type.html
new file mode 100644
index 0000000..5278701
--- /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');
+
+</%init>
diff --git a/httemplate/edit/process/svc_dish.html b/httemplate/edit/process/svc_dish.html
new file mode 100644
index 0000000..6c8851e
--- /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?
+
+</%init>
diff --git a/httemplate/edit/process/svc_hardware.html b/httemplate/edit/process/svc_hardware.html
new file mode 100644
index 0000000..5abf16c
--- /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?
+
+</%init>
diff --git a/httemplate/edit/svc_dish.cgi b/httemplate/edit/svc_dish.cgi
new file mode 100644
index 0000000..77a2239
--- /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',
+ },
+
+);
+
+</%init>
diff --git a/httemplate/edit/svc_hardware.cgi b/httemplate/edit/svc_hardware.cgi
new file mode 100644
index 0000000..e6cb22b
--- /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',
+ },
+
+);
+
+</%init>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index a70abe4..f558777 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 0000000..692b3c9
--- /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 = @_;
+</%init>
diff --git a/httemplate/elements/select-hardware_type.html b/httemplate/elements/select-hardware_type.html
new file mode 100644
index 0000000..ae07798
--- /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;
+
+</%init>
diff --git a/httemplate/elements/tr-cust_svc.html b/httemplate/elements/tr-cust_svc.html
new file mode 100644
index 0000000..e792ff3
--- /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.
+</%doc>
+<TR>
+ <TD ALIGN="right" VALIGN="top"><%
+FS::UI::Web::svc_link($m, $part_svc, $cust_svc)
+%></TD>
+ <TD STYLE="padding-bottom:0px"><B><%
+FS::UI::Web::svc_label_link($m, $part_svc, $cust_svc)
+%></B></TD>
+</TR>
+% if ( $cust_svc->overlimit ) {
+<TR>
+ <TD ALIGN="right" COLSPAN="3" VALIGN="top"
+ STYLE="padding-bottom:1px; padding-top:0px">
+ <FONT SIZE="-2" COLOR="#FFD000">Overlimit: <%
+time2str('%b %o %Y' . $opt{'cust_pkg-display_times'} ? ' %l:%M %P' : '',
+$cust_svc->overlimit )
+ %></FONT>
+ </TD>
+</TR>
+% }
+<TR>
+% # first column: recharge link
+ <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px; padding-top:0px">
+% 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 ''
+% )
+% ) {
+ <FONT SIZE="-2">(&nbsp;<% svc_recharge_link($cust_svc)%>&nbsp;)</FONT>
+% }
+ </TD>
+% # second column: all other action links
+ <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px; padding-top:0px">
+% if ( $part_svc->svcdb eq 'svc_broadband' ) {
+ <FONT SIZE="-1" STYLE="float:left">(&nbsp;<%
+ include('/elements/popup_link-ping.html',
+ 'ip' => $svc_x->ip_addr
+ ) %>&nbsp;)</FONT>
+% my $manage_link = $opt{'svc_broadband-manage_link'};
+% if ( $manage_link ) {
+ <FONT SIZE="-1" STYLE="float:left">(&nbsp;<A HREF="<%
+ eval(qq("$manage_link"))
+ %>">Manage Device</A>&nbsp;)</FONT>
+% }
+% } #svc_broadband
+% if ( $curuser->access_right('Unprovision customer service') ) {
+ <FONT SIZE="-2">(&nbsp;<% $svc_unprovision_link %>&nbsp;)</FONT>
+% }
+% if ( $part_svc->svcdb eq 'svc_pbx' && $opt{'maestro-status_test'} ) {
+ <FONT SIZE="-2">(&nbsp;<A HREF="<%$p%>misc/maestro-customer_status-test.html?<% $cust_pkg->custnum.'+'.$cust_svc->svcnum %>">Test maestro status</A>&nbsp;)
+ </FONT>
+% }
+ </TD>
+</TR>
+
+<%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!<A HREF="javascript:areyousure('${p}misc/unprovision.cgi?! .
+ $cust_svc->svcnum .
+ qq!', 'Permanently unprovision and delete this service?')">Unprovision</A>!;
+
+</%init>
diff --git a/httemplate/elements/tr-cust_svc_cancel.html b/httemplate/elements/tr-cust_svc_cancel.html
new file mode 100644
index 0000000..e7fa47a
--- /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.
+</%doc>
+<TR STYLE="color:#cccccc;">
+ <TD ALIGN="right" VALIGN="top"><%
+FS::UI::Web::svc_link($m, $part_svc, $cust_svc)
+%></TD>
+ <TD STYLE="padding-bottom:0px;"><B><%
+FS::UI::Web::svc_label_link($m, $part_svc, $cust_svc)
+%></B></TD>
+</TR>
+%# 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;
+
+</%init>
diff --git a/httemplate/elements/tr-select-hardware_type.html b/httemplate/elements/tr-select-hardware_type.html
new file mode 100644
index 0000000..c306641
--- /dev/null
+++ b/httemplate/elements/tr-select-hardware_type.html
@@ -0,0 +1,10 @@
+<TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Device type: ' %></TD>
+ <TD><% include('select-hardware_type.html', %opt) %></TD>
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/search/report_svc_hardware.html b/httemplate/search/report_svc_hardware.html
new file mode 100755
index 0000000..4a763b0
--- /dev/null
+++ b/httemplate/search/report_svc_hardware.html
@@ -0,0 +1,71 @@
+<% include('/elements/header.html', $title ) %>
+
+<FORM ACTION="svc_hardware.cgi" METHOD="GET">
+
+ <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+ <TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Search options</FONT></TH>
+ </TR>
+
+ <TR><TD>
+ <% include('/elements/selectlayers.html',
+ 'field' => 'classnum',
+ 'label' => '',
+ 'options' => \@classnums,
+ 'labels' => \%class_labels,
+ 'layer_callback' => \&layer_callback,
+ 'html_between' => '</TD><TD>',
+ ) %>
+ </TD></TR>
+
+ <% 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',
+ ) %>
+
+ </TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Search">
+
+</FORM>
+
+<% 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',
+ );
+}
+
+</%init>
+
diff --git a/httemplate/search/svc_dish.cgi b/httemplate/search/svc_dish.cgi
new file mode 100755
index 0000000..94da035
--- /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' ] : '';
+};
+
+</%init>
diff --git a/httemplate/search/svc_hardware.cgi b/httemplate/search/svc_hardware.cgi
new file mode 100644
index 0000000..ffbb9f3
--- /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' ];
+
+</%init>
diff --git a/httemplate/view/cust_main/packages/services.html b/httemplate/view/cust_main/packages/services.html
index 512efcc..1e636ad 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) {
}
</SCRIPT>
-% #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) {
</TD>
</TR>
% }
-% else {
+% else { # don't summarize
% foreach my $cust_svc ( @{ $part_svc->cust_pkg_svc } ) {
-
- <TR>
- <TD ALIGN="right" VALIGN="top"><% FS::UI::Web::svc_link($m, $part_svc, $cust_svc) %></TD>
- <TD STYLE="padding-bottom:0px"><B><% FS::UI::Web::svc_label_link($m, $part_svc, $cust_svc) %></B></TD>
- <TD><% FS::UI::Web::svc_export_links($m, $part_svc, $cust_svc) %></TD>
- </TR>
-
- <TR>
- <TD ALIGN="right" COLSPAN="3" VALIGN="top" STYLE="padding-bottom:1px;padding-top:0px"><FONT SIZE="-2" COLOR="#FFD000">
-
- <% $cust_svc->overlimit ? "Overlimit: ". time2str('%b %o %Y' . ($opt{'cust_pkg-display_times'} ? ' %l:%M %P' : ''), $cust_svc->overlimit) : '' %>
- </FONT></TD>
- </TR>
-
- <TR>
- <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px"><FONT SIZE="-2">
-
-% 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 ''
-% )
-% ) {
- (&nbsp;<%svc_recharge_link($cust_svc)%>&nbsp;)
-% }
- </FONT></TD>
-
- <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px">
-
-% my $ip_addr = $cust_svc->svc_x->ip_addr;
-
-% if ( $part_svc->svcdb eq 'svc_broadband' ) {
- <FONT SIZE="-1" STYLE="float:left">(&nbsp;<% include('/elements/popup_link-ping.html', 'ip'=> $ip_addr ) %>&nbsp;)</FONT>
-
-% }
-
-% 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"));
- <FONT SIZE="-1" STYLE="float:left">(&nbsp;<A HREF="<% $svc_manage_link %>">Manage Device</A>&nbsp;)</FONT>
-
-% }
-
-% if ( $curuser->access_right('Unprovision customer service') ) {
- <FONT SIZE="-2">(&nbsp;<%svc_unprovision_link($cust_svc)%>&nbsp;)</FONT>
-% }
-
-% if ( $part_svc->svcdb eq 'svc_pbx' && $opt{'maestro-status_test'} ){
- <FONT SIZE="-2">(&nbsp;<A HREF="<% $p %>misc/maestro-customer_status-test.html?<% $cust_pkg->custnum.'+'.$cust_svc->svcnum %>">Test maestro status</A>&nbsp;)</FONT>
-% }
-
- </TD>
- </TR>
+% 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!<A HREF="javascript:areyousure('${p}misc/unprovision.cgi?!. $cust_svc->svcnum.
- qq!', 'Permanently unprovision and delete this service?')">Unprovision</A>!;
-}
-
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 de49b50..7a7539d 100644
--- a/httemplate/view/elements/svc_Common.html
+++ b/httemplate/view/elements/svc_Common.html
@@ -51,10 +51,7 @@ function areyousure(href) {
Service #<B><% $svcnum %></B>
% my $url = $opt{'edit_url'} || $p. 'edit/'. $opt{'table'}. '.cgi?';
-| <A HREF="<%$url%><%$svcnum%>">Edit this <% $label %></A>
-
-| <A HREF="javascript:areyousure('<%$p.'misc/unprovision.cgi?'.$svcnum%>')">
-Unprovision this Service</A>
+| <% include('/view/elements/svc_edit_link.html', 'svc' => $svc_x) %>
<BR>
<% ntable("#cccccc") %><TR><TD><% 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 0000000..a85d380
--- /dev/null
+++ b/httemplate/view/elements/svc_edit_link.html
@@ -0,0 +1,24 @@
+% if ( $cancel_date ) {
+<I>Canceled <% time2str('%b %o %Y', $cancel_date) %></I>
+% } else {
+<SCRIPT>
+function areyousure_delete() {
+ if (confirm("Permanently delete this service?") == true)
+ window.location.href = '<% $cancel_url %>';
+}
+</SCRIPT>
+<A HREF="<% $edit_url %>">Edit this <% $label %></A> |
+<A HREF="javascript:areyousure_delete()">
+Unprovision this Service</A>
+% }
+<%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;
+</%init>
diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi
index 4e82569..291298d 100755
--- a/httemplate/view/svc_acct.cgi
+++ b/httemplate/view/svc_acct.cgi
@@ -38,8 +38,8 @@
%>
Service #<B><% $svcnum %></B>
-| <A HREF="<%$p%>edit/svc_acct.cgi?<%$svcnum%>">Edit this service</A>
-
+|
+<% 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 12e5f0f..2c44293 100644
--- a/httemplate/view/svc_broadband.cgi
+++ b/httemplate/view/svc_broadband.cgi
@@ -10,7 +10,7 @@
<% include('/elements/init_overlib.html') %>
-<A HREF="<%$p%>edit/svc_broadband.cgi?<%$svcnum%>">Edit this information</A>
+<% include('/view/elements/svc_edit_link.html', $svc_acct) %>
<BR>
<%ntable("#cccccc")%>
<TR>
diff --git a/httemplate/view/svc_dish.cgi b/httemplate/view/svc_dish.cgi
new file mode 100644
index 0000000..d4aa8bf
--- /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 );
+</%init>
diff --git a/httemplate/view/svc_hardware.cgi b/httemplate/view/svc_hardware.cgi
new file mode 100644
index 0000000..9cea341
--- /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' );
+</%init>
diff --git a/httemplate/view/svc_phone.cgi b/httemplate/view/svc_phone.cgi
index e4dc335..4a850c2 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";