svc_hardware and svc_dish, #11454
authormark <mark>
Fri, 1 Apr 2011 02:52:24 +0000 (02:52 +0000)
committermark <mark>
Fri, 1 Apr 2011 02:52:24 +0000 (02:52 +0000)
52 files changed:
FS/FS/Mason.pm
FS/FS/Record.pm
FS/FS/Schema.pm
FS/FS/cust_pkg.pm
FS/FS/cust_svc.pm
FS/FS/hardware_class.pm [new file with mode: 0644]
FS/FS/hardware_status.pm [new file with mode: 0644]
FS/FS/hardware_type.pm [new file with mode: 0644]
FS/FS/part_svc.pm
FS/FS/part_svc_column.pm
FS/FS/svc_dish.pm [new file with mode: 0644]
FS/FS/svc_hardware.pm [new file with mode: 0644]
FS/MANIFEST
FS/t/hardware_class.t [new file with mode: 0644]
FS/t/hardware_status.t [new file with mode: 0644]
FS/t/hardware_type.t [new file with mode: 0644]
FS/t/svc_dish.t [new file with mode: 0644]
FS/t/svc_hardware.t [new file with mode: 0644]
httemplate/browse/hardware_class.html [new file with mode: 0644]
httemplate/browse/hardware_status.html [new file with mode: 0644]
httemplate/browse/part_svc.cgi
httemplate/edit/elements/edit.html
httemplate/edit/elements/svc_Common.html
httemplate/edit/hardware_class.html [new file with mode: 0644]
httemplate/edit/hardware_status.html [new file with mode: 0644]
httemplate/edit/hardware_type.html [new file with mode: 0644]
httemplate/edit/part_svc.cgi
httemplate/edit/process/elements/process.html
httemplate/edit/process/hardware_class.html [new file with mode: 0644]
httemplate/edit/process/hardware_status.html [new file with mode: 0644]
httemplate/edit/process/hardware_type.html [new file with mode: 0644]
httemplate/edit/process/svc_dish.html [new file with mode: 0644]
httemplate/edit/process/svc_hardware.html [new file with mode: 0644]
httemplate/edit/svc_dish.cgi [new file with mode: 0644]
httemplate/edit/svc_hardware.cgi [new file with mode: 0644]
httemplate/elements/menu.html
httemplate/elements/select-hardware_class.html [new file with mode: 0644]
httemplate/elements/select-hardware_type.html [new file with mode: 0644]
httemplate/elements/tr-cust_svc.html [new file with mode: 0644]
httemplate/elements/tr-cust_svc_cancel.html [new file with mode: 0644]
httemplate/elements/tr-select-hardware_type.html [new file with mode: 0644]
httemplate/search/report_svc_hardware.html [new file with mode: 0755]
httemplate/search/svc_dish.cgi [new file with mode: 0755]
httemplate/search/svc_hardware.cgi [new file with mode: 0644]
httemplate/view/cust_main/packages/services.html
httemplate/view/elements/svc_Common.html
httemplate/view/elements/svc_edit_link.html [new file with mode: 0644]
httemplate/view/svc_acct.cgi
httemplate/view/svc_broadband.cgi
httemplate/view/svc_dish.cgi [new file with mode: 0644]
httemplate/view/svc_hardware.cgi [new file with mode: 0644]
httemplate/view/svc_phone.cgi

index ccab793..dd18717 100644 (file)
@@ -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 ) {
index fb83faa..7b52f50 100644 (file)
@@ -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.
index 25eafa3..66847b6 100644 (file)
@@ -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',     '',  '', '', '', 
index 2efd4fd..13a3b6e 100644 (file)
@@ -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 ) {
index 30e49c5..8cce7af 100644 (file)
@@ -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 (file)
index 0000000..073a97f
--- /dev/null
@@ -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 (file)
index 0000000..4836fc5
--- /dev/null
@@ -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 (file)
index 0000000..ba19fcb
--- /dev/null
@@ -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;
+
index 164bad0..e15b225 100644 (file)
@@ -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$/ ) {
index f5b39c0..d467516 100644 (file)
@@ -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 (file)
index 0000000..5dac4f4
--- /dev/null
@@ -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 (file)
index 0000000..5b81a9c
--- /dev/null
@@ -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;
+
index fce7903..a293451 100644 (file)
@@ -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 (file)
index 0000000..8c98234
--- /dev/null
@@ -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 (file)
index 0000000..71b3077
--- /dev/null
@@ -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 (file)
index 0000000..072ed7c
--- /dev/null
@@ -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 (file)
index 0000000..684837b
--- /dev/null
@@ -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 (file)
index 0000000..83ca816
--- /dev/null
@@ -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 (file)
index 0000000..aef0fa3
--- /dev/null
@@ -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 (file)
index 0000000..9695ed3
--- /dev/null
@@ -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>
index 82b1150..4549e44 100755 (executable)
@@ -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>
index 1ed75c3..295ad85 100644 (file)
@@ -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'},
index aa76995..0955d49 100644 (file)
                        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 (file)
index 0000000..8760dd8
--- /dev/null
@@ -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 (file)
index 0000000..ee5f25d
--- /dev/null
@@ -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 (file)
index 0000000..09a2724
--- /dev/null
@@ -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>
index d608657..97e2d96 100755 (executable)
@@ -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">
index 53419cd..107b3f2 100644 (file)
@@ -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 (file)
index 0000000..64bc72e
--- /dev/null
@@ -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 (file)
index 0000000..61f02e2
--- /dev/null
@@ -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 (file)
index 0000000..5278701
--- /dev/null
@@ -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 (file)
index 0000000..6c8851e
--- /dev/null
@@ -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 (file)
index 0000000..5abf16c
--- /dev/null
@@ -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 (file)
index 0000000..77a2239
--- /dev/null
@@ -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 (file)
index 0000000..e6cb22b
--- /dev/null
@@ -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>
index a70abe4..f558777 100644 (file)
@@ -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 (file)
index 0000000..692b3c9
--- /dev/null
@@ -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 (file)
index 0000000..ae07798
--- /dev/null
@@ -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 (file)
index 0000000..e792ff3
--- /dev/null
@@ -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 (file)
index 0000000..e7fa47a
--- /dev/null
@@ -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 (file)
index 0000000..c306641
--- /dev/null
@@ -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 (executable)
index 0000000..4a763b0
--- /dev/null
@@ -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 (executable)
index 0000000..94da035
--- /dev/null
@@ -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 (file)
index 0000000..ffbb9f3
--- /dev/null
@@ -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>
index 512efcc..1e636ad 100644 (file)
@@ -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)',
index de49b50..7a7539d 100644 (file)
@@ -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 (file)
index 0000000..a85d380
--- /dev/null
@@ -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>
index 4e82569..291298d 100755 (executable)
@@ -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) {
index 12e5f0f..2c44293 100644 (file)
@@ -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 (file)
index 0000000..d4aa8bf
--- /dev/null
@@ -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 (file)
index 0000000..9cea341
--- /dev/null
@@ -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>
index e4dc335..4a850c2 100644 (file)
@@ -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";