[freeside-commits] branch FREESIDE_2_3_BRANCH updated. e6c35a697d46eb5b47c5398daaf15d04f7c9022f

Ivan ivan at 420.am
Thu Sep 26 17:45:46 PDT 2013


The branch, FREESIDE_2_3_BRANCH has been updated
       via  e6c35a697d46eb5b47c5398daaf15d04f7c9022f (commit)
       via  9113d56728b4e55606daa6316066c469b08a9fc7 (commit)
       via  a633b5ba5364a69bffb507a0a48331ba879ed0ac (commit)
       via  361a226ba50dc6621f258a1435defddb39f413d2 (commit)
       via  b46a09ef4cce4ddbd404e6e6347baffde8170716 (commit)
       via  5e5be65156dc03c73ac173af38329c187276d820 (commit)
       via  6ca44d5ef4eef39d742838cc538b9da2e277c01e (commit)
       via  70999e67772fce6666191ec3fa95d9afb5f5b799 (commit)
       via  7869fb435b3269128be4750556aa1a16bd3dfde3 (commit)
       via  a6168cf714d778bbaa9637c1d344b821dd153dd1 (commit)
       via  34b6066b473530debe318fb48718dc2193cd6f85 (commit)
       via  a97e5cbca3e7b9e7d59ecbe34057426b2ff7b34d (commit)
      from  486a5bc9dce792b34889411617ef7ebf535b93c7 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit e6c35a697d46eb5b47c5398daaf15d04f7c9022f
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue Sep 10 01:42:32 2013 -0700

    svc_cable service have a single serial / MAC / model, not one-to-many devices like svc_phone and svc_dsl, RT#22009

diff --git a/httemplate/view/svc_Common.html b/httemplate/view/svc_Common.html
index 7b46dc9..4076987 100644
--- a/httemplate/view/svc_Common.html
+++ b/httemplate/view/svc_Common.html
@@ -25,6 +25,17 @@ if ( UNIVERSAL::can("FS::$table", 'table_info') ) {
                    }
                keys %$fields;
   $opt{'labels'} = \%labels;
+
+  #transform FS::svc_* table_info's structure into one svc_Common.html likes
+  delete $fields->{svcnum};
+  $opt{'fields'} = [ map { ref $fields->{$_}
+                             ? { field => $_,
+                                 %{ $fields->{$_} }
+                               }
+                             : $_
+                          } keys %$fields
+                   ];
+
 }
 
 </%init>

commit 9113d56728b4e55606daa6316066c469b08a9fc7
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue Sep 10 01:34:06 2013 -0700

    svc_cable service have a single serial / MAC / model, not one-to-many devices like svc_phone and svc_dsl, RT#22009

diff --git a/FS/FS/cable_model.pm b/FS/FS/cable_model.pm
new file mode 100644
index 0000000..7f662de
--- /dev/null
+++ b/FS/FS/cable_model.pm
@@ -0,0 +1,109 @@
+package FS::cable_model;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::cable_model - Object methods for cable_model records
+
+=head1 SYNOPSIS
+
+  use FS::cable_model;
+
+  $record = new FS::cable_model \%hash;
+  $record = new FS::cable_model { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cable_model object represents a cable device model.  FS::cable_model
+inherits from FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item modelnum
+
+primary key
+
+=item model_name
+
+model_name
+
+=item disabled
+
+disabled
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record.  To add the record to the database, see L<"insert">.
+
+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
+
+sub table { 'cable_model'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid record.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('modelnum')
+    || $self->ut_text('model_name')
+    || $self->ut_enum('disabled', [ '', 'Y'] )
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/t/cable_model.t b/FS/t/cable_model.t
new file mode 100644
index 0000000..cff872a
--- /dev/null
+++ b/FS/t/cable_model.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cable_model;
+$loaded=1;
+print "ok 1\n";
diff --git a/httemplate/browse/cable_model.html b/httemplate/browse/cable_model.html
new file mode 100644
index 0000000..af98d89
--- /dev/null
+++ b/httemplate/browse/cable_model.html
@@ -0,0 +1,33 @@
+<& elements/browse.html,
+     'title'              => 'Cable modem models',
+     'html_init'          => $html_init,
+     'name'               => 'models',
+     'disableable'        => 1,
+     'disabled_statuspos' => 1,
+     'query'              => { 'table'     => 'cable_model',
+                               'hashref'   => {},
+                               'order_by' => 'ORDER BY model_name',
+                             },
+     'count_query'        => $count_query,
+     'header'             => $header,
+     'fields'             => $fields,
+     'links'              => $links,
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init =
+  #'Cable "modem" models.<BR><BR>'.
+  qq!<A HREF="${p}edit/cable_model.html"><I>Add a model</I></A><BR><BR>!;
+
+my $count_query = 'SELECT COUNT(*) FROM cable_model';
+
+my $link = [ $p.'edit/cable_model.html?', 'modelnum' ];
+
+my $header = [ 'Model' ];
+my $fields = [ 'model_name' ];
+my $links  = [ $link ];
+
+</%init>
diff --git a/httemplate/edit/cable_model.html b/httemplate/edit/cable_model.html
new file mode 100644
index 0000000..0bda4e8
--- /dev/null
+++ b/httemplate/edit/cable_model.html
@@ -0,0 +1,20 @@
+<& elements/edit.html,
+     'name_singular' => 'Model',
+     'table'         => 'cable_model',
+     'fields'        => [
+                          'model_name',
+                          { field=>'disabled', type=>'checkbox', value=>'Y', },
+                        ],
+     'labels'        => {
+                          'modelnum'   => 'Model',
+                          'model_name' => 'Model',
+                          'disabled'   => 'Disabled',
+                        },
+     'viewall_dir'   => 'browse',
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/cable_model.html b/httemplate/edit/process/cable_model.html
new file mode 100644
index 0000000..072df02
--- /dev/null
+++ b/httemplate/edit/process/cable_model.html
@@ -0,0 +1,10 @@
+<& elements/process.html,
+     'table'       => 'cable_model',
+     'viewall_dir' => 'browse',
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/elements/select-cable_model.html b/httemplate/elements/select-cable_model.html
new file mode 100644
index 0000000..8d65a39
--- /dev/null
+++ b/httemplate/elements/select-cable_model.html
@@ -0,0 +1,7 @@
+<% include( '/elements/select-table.html',
+    'table'            => 'cable_model',
+    'name_col'         => 'model_name',
+    'empty_label'      => 'Select model',
+    @_,
+   )
+%>
diff --git a/httemplate/elements/tr-select-cable_model.html b/httemplate/elements/tr-select-cable_model.html
new file mode 100644
index 0000000..94b5cd9
--- /dev/null
+++ b/httemplate/elements/tr-select-cable_model.html
@@ -0,0 +1,12 @@
+% #if ( scalar(@domains) < 2 ) {
+% #} else {
+  <TR>
+    <TD ALIGN="right"><% $opt{'label'} || 'Model' %></TD>
+    <TD>
+      <% include( '/elements/select-cable_model.html', %opt) %>
+    </TD>
+   </TR>
+% #}
+<%init>
+  my %opt = @_;
+</%init>

commit a633b5ba5364a69bffb507a0a48331ba879ed0ac
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue Sep 10 01:33:44 2013 -0700

    svc_cable service have a single serial / MAC / model, not one-to-many devices like svc_phone and svc_dsl, RT#22009

diff --git a/FS/FS/cable_device.pm b/FS/FS/cable_device.pm
deleted file mode 100644
index 1a0f1b9..0000000
--- a/FS/FS/cable_device.pm
+++ /dev/null
@@ -1,140 +0,0 @@
-package FS::cable_device;
-
-use strict;
-use base qw( FS::Record );
-use FS::Record qw( qsearchs ); # qsearch );
-use FS::part_device;
-use FS::svc_cable;
-
-=head1 NAME
-
-FS::cable_device - Object methods for cable_device records
-
-=head1 SYNOPSIS
-
-  use FS::cable_device;
-
-  $record = new FS::cable_device \%hash;
-  $record = new FS::cable_device { 'column' => 'value' };
-
-  $error = $record->insert;
-
-  $error = $new_record->replace($old_record);
-
-  $error = $record->delete;
-
-  $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cable_device object represents a specific customer cable modem.
-FS::cable_device inherits from FS::Record.  The following fields are currently
-supported:
-
-=over 4
-
-=item devicenum
-
-primary key
-
-=item devicepart
-
-devicepart
-
-=item svcnum
-
-svcnum
-
-=item mac_addr
-
-mac_addr
-
-=item serial
-
-serial
-
-
-=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
-
-sub table { 'cable_device'; }
-
-=item insert
-
-Adds this record to the database.  If there is an error, returns the error,
-otherwise returns false.
-
-=item delete
-
-Delete this record from the database.
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database.  If there is an error,
-returns the error, otherwise returns false.
-
-=item check
-
-Checks all fields to make sure this is a valid record.  If there is
-an error, returns the error, otherwise returns false.  Called by the insert
-and replace methods.
-
-=cut
-
-sub check {
-  my $self = shift;
-
-  my $mac = $self->mac_addr;
-  $mac =~ s/\s+//g;
-  $mac =~ s/://g;
-  $self->mac_addr($mac);
-
-  my $error = 
-    $self->ut_numbern('devicenum')
-    || $self->ut_number('devicepart')
-    || $self->ut_foreign_key('devicepart', 'part_device', 'devicepart')
-    || $self->ut_foreign_key('svcnum', 'svc_cable', 'svcnum' ) #cust_svc?
-    || $self->ut_hexn('mac_addr')
-    || $self->ut_textn('serial')
-  ;
-  return $error if $error;
-
-  $self->SUPER::check;
-}
-
-=item part_device
-
-Returns the device type record (see L<FS::part_device>) associated with this
-customer device.
-
-=cut
-
-sub part_device {
-  my $self = shift;
-  qsearchs( 'part_device', { 'devicepart' => $self->devicepart } );
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>
-
-=cut
-
-1;
-
diff --git a/FS/t/cable_device.t b/FS/t/cable_device.t
deleted file mode 100644
index 016d2c5..0000000
--- a/FS/t/cable_device.t
+++ /dev/null
@@ -1,5 +0,0 @@
-BEGIN { $| = 1; print "1..1\n" }
-END {print "not ok 1\n" unless $loaded;}
-use FS::cable_device;
-$loaded=1;
-print "ok 1\n";
diff --git a/httemplate/edit/cable_device.html b/httemplate/edit/cable_device.html
deleted file mode 100644
index 963bbf7..0000000
--- a/httemplate/edit/cable_device.html
+++ /dev/null
@@ -1,117 +0,0 @@
-<% include( 'elements/edit.html',
-                 'name'   => 'Cable device',
-                 'table'  => 'cable_device',
-                 'labels' => { 
-                               'devicenum'  => 'Device',
-                               'devicepart' => 'Device type',
-                               'mac_addr'   => 'MAC address',
-                               'serial'     => 'Serial number',
-                             },
-                 'fields' => [ { 'field'    => 'devicepart',
-                                 'type'     => 'select-table',
-                                 'table'    => 'part_device',
-                                 'name_col' => 'devicename',
-				 'onchange' => 'devicepart_changed',
-                                 'empty_label' =>'Select device type',
-                                 #'hashref'        =>{ disabled => '' },
-                               },
-			       { field => 'mac_addr',
-			         type => 'select-mac',
-			       },
-			       { field => 'serial',
-			         type  => 'text', #select-serial
-			       },
-                               { 'field' => 'svcnum',
-                                 'type'  => 'hidden',
-                               },
-                             ],
-                 'menubar' => [], #disable viewall
-                 #'viewall_dir' => 'browse',
-                 'new_callback' => sub {
-                                     my( $cgi, $object ) = @_;
-                                     $object->svcnum( $cgi->param('svcnum') );
-                                   },
-		 'html_foot' => $html_foot,
-           )
-%>
-<%init>
-
-#bad: pretty much entirely false laziness w/phone_device, except for labels and
-# the serial field
-
-my @deviceparts_with_inventory =
-  map $_->devicepart,
-    qsearch({ 'table'     => 'part_device',
-              'extra_sql' => 'WHERE inventory_classnum IS NOT NULL',
-           });
-
-my $html_foot = sub {
-    my $js = "
-<SCRIPT TYPE=\"text/javascript\">
-
-  function opt(what,value,text) {
-    var optionName = new Option(text, value, false, false);
-    var length = what.length;
-    what.options[length] = optionName;
-  }
-
-    function devicepart_changed(what){
-	
-	var macsel = document.getElementById('sel_mac_addr');
-	var mac = document.getElementById('mac_addr');
-	
-	function update_macs(macs) {
-	    for ( var i = macsel.length; i >= 0; i-- )
-	      macsel.options[i] = null;
-	    
-	    var macArray = eval('(' + macs + ')' );
-	    if(macArray.length == 0) 
-		opt(macsel,'','No MAC addresses found in inventory for this device type');
-	    else
-		opt(macsel,'','Select MAC address');
-
-	    for ( var i = 0; i < macArray.length; i++ ) {
-		opt(macsel,macArray[i],macArray[i]);
-	    }
-
-	}
-
-	var devicepart = what.options[what.selectedIndex].value;
-
-	var deviceparts_with_inventory = new Array(";
-$js .= join(',', map qq("$_"), @deviceparts_with_inventory);
-$js .= ");
-
-	var hasInventory = false;
-	for ( i = 0; i < deviceparts_with_inventory.length; i++ ) {
-	    if ( deviceparts_with_inventory[i] == devicepart ) 
-		hasInventory = true;
-	}
-	
-
-	if(hasInventory) { // do the AJAX thing, disable text field
-	    macsel.style.display = 'inline';
-	    mac.style.display = 'none';
-	    mac.value = '';
-	    get_macs( devicepart, update_macs );
-	} else { // clear & display text field only, clear/hide select
-	    mac.style.display = 'inline';
-	    macsel.style.display = 'none';
-	    macsel.selectedIndex = 0;
-	}
-
-    }
-
-    devicepart_changed(document.getElementById('devicepart'));
-</SCRIPT>";
-
-  $js;
-};
-
-# :/  needs agent-virt so you can't futz with arbitrary devices
-
-die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
-
-
-</%init>
diff --git a/httemplate/edit/process/cable_device.html b/httemplate/edit/process/cable_device.html
deleted file mode 100644
index 97b4f81..0000000
--- a/httemplate/edit/process/cable_device.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<% include( 'elements/process.html',
-               'table'    => 'cable_device',
-               'redirect' => sub {
-                 my( $cgi, $cable_device ) = @_;
-                 #popurl(3).'view/svc_cable.html?'.
-                 popurl(3).'view/svc_Common.html?svcdb=svc_cable;'.
-                   'svcnum='. $cable_device->svcnum.
-                   ';devicenum=';
-               },
-           )
-%>
-<%init>
-
-if($cgi->param('sel_mac_addr') && !$cgi->param('mac_addr')) {
-    $cgi->param('mac_addr',$cgi->param('sel_mac_addr'));
-}
-
-# :/  needs agent-virt so you can't futz with arbitrary devices
-
-die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
-
-</%init>

commit 361a226ba50dc6621f258a1435defddb39f413d2
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Thu Sep 26 17:45:14 2013 -0700

    svc_cable service have a single serial / MAC / model, not one-to-many devices like svc_phone and svc_dsl (2.3 backport), RT#22009

diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi
index 3f8d485..fe2dfde 100755
--- a/httemplate/edit/part_svc.cgi
+++ b/httemplate/edit/part_svc.cgi
@@ -337,9 +337,9 @@ Self-service access:
 %            qq!<TEXTAREA NAME="${layer}__${field}">!. encode_entities($value).
 %            '</TEXTAREA>';
 %
-%        } elsif ( $def->{type} =~ /select-(.*?).html/ ) {
+%        } elsif ( $def->{type} =~ /select-(.*?)(.html)?$/ && $1 ne 'hardware' ) {
 %
-%          $html .= include("/elements/".$def->{type},
+%          $html .= include("/elements/select-$1.html",
 %                             'curr_value'   => $value,
 %                             'element_name' => "${layer}__${field}",
 %                             'element_etc'  => $disabled,

commit b46a09ef4cce4ddbd404e6e6347baffde8170716
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue Sep 10 01:33:32 2013 -0700

    svc_cable service have a single serial / MAC / model, not one-to-many devices like svc_phone and svc_dsl (2.3 backport), RT#22009
    
    Conflicts:
    	FS/FS/Mason.pm
    	FS/FS/svc_broadband.pm
    	FS/MANIFEST
    	httemplate/edit/elements/part_svc_column.html
    	httemplate/elements/menu.html

diff --git a/FS/FS.pm b/FS/FS.pm
index 2dcc796..75656ec 100644
--- a/FS/FS.pm
+++ b/FS/FS.pm
@@ -208,6 +208,8 @@ L<FS::svc_dish> - Dish network service class
 
 L<FS::svc_cable> - Cable service class
 
+L<FS::cable_model> - Cable-model model class
+
 L<FS::inventory_class> - Inventory classes
 
 L<FS::inventory_item> - Inventory items
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index 6f6b74d..0f8ae87 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -316,7 +316,7 @@ if ( -e $addl_handler_use_file ) {
   use FS::log;
   use FS::log_context;
   use FS::svc_cable;
-  use FS::cable_device;
+  use FS::cable_model;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {
diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm
index 83c55b6..5b69127 100755
--- a/FS/FS/svc_broadband.pm
+++ b/FS/FS/svc_broadband.pm
@@ -292,10 +292,14 @@ sub search_sql {
   my( $class, $string ) = @_;
   if ( $string =~ /^(\d{1,3}\.){3}\d{1,3}$/ ) {
     $class->search_sql_field('ip_addr', $string );
-  }elsif ( $string =~ /^([a-fA-F0-9]{12})$/ ) {
+  } elsif ( $string =~ /^([A-F0-9]{12})$/i ) {
     $class->search_sql_field('mac_addr', uc($string));
-  }elsif ( $string =~ /^(([a-fA-F0-9]{1,2}:){5}([a-fA-F0-9]{1,2}))$/ ) {
-    $class->search_sql_field('mac_addr', uc("$2$3$4$5$6$7") );
+  } elsif ( $string =~ /^(([A-F0-9]{2}:){5}([A-F0-9]{2}))$/i ) {
+    $string =~ s/://g;
+    $class->search_sql_field('mac_addr', uc($string) );
+  } elsif ( $string =~ /^(\d+)$/ ) {
+    my $table = $class->table;
+    "$table.svcnum = $1";
   } else {
     '1 = 0'; #false
   }
diff --git a/FS/FS/svc_cable.pm b/FS/FS/svc_cable.pm
index f588f43..1980c0e 100644
--- a/FS/FS/svc_cable.pm
+++ b/FS/FS/svc_cable.pm
@@ -1,9 +1,10 @@
 package FS::svc_cable;
-use base qw( FS::device_Common FS::svc_Common );
+use base qw( FS::svc_Common ); #qw( FS::device_Common FS::svc_Common );
 
 use strict;
-use base qw( FS::Record );
-use FS::Record; # qw( qsearch qsearchs );
+use Tie::IxHash;
+use FS::Record qw( qsearchs ); # qw( qsearch qsearchs );
+use FS::cable_model;
 
 =head1 NAME
 
@@ -52,18 +53,53 @@ points to.  You can ask the object for a copy with the I<hash> method.
 
 sub table { 'svc_cable'; }
 
+sub table_dupcheck_fields { ( 'mac_addr' ); }
+
+sub search_sql {
+  my( $class, $string ) = @_;
+  if ( $string =~ /^([A-F0-9]{12})$/i ) {
+    $class->search_sql_field('mac_addr', uc($string));
+  } elsif ( $string =~ /^(([A-F0-9]{2}:){5}([A-F0-9]{2}))$/i ) {
+    $string =~ s/://g;
+    $class->search_sql_field('mac_addr', uc($string) );
+  } elsif ( $string =~ /^(\w+)$/ ) {
+    $class->search_sql_field('serialnum', $1);
+  } else {
+    '1 = 0'; #false
+  }
+}
+
 sub table_info {
+
+  tie my %fields, 'Tie::IxHash',
+    'svcnum'     => 'Service',
+    'modelnum'   => { label             => 'Model',
+                      type              => 'select-cable_model',
+                      disable_inventory => 1,
+                      disable_select    => 1,
+                      value_callback    => sub {
+                                             my $svc = shift;
+                                             $svc->cable_model->model_name;
+                                           },
+                    },
+    'serialnum'  => 'Serial number',
+    'mac_addr'   => { label          => 'MAC address',
+                      type           => 'input-mac_addr',
+                      value_callback => sub {
+                                          my $svc = shift;
+                                          join(':', $svc->mac_addr =~ /../g);
+                                        },
+                    },
+  ;
+
   {
-    'name' => 'Cable Subscriber',
-    #'name_plural' => '', #optional,
+    'name'            => 'Cable Subscriber',
+    #'name_plural'     => '', #optional,
     #'longname_plural' => '', #optional
-    'sorts' => [ 'svcnum', ], #, 'serviceid' ], # optional sort field (or arrayref of sort fields, main first)
-    'display_weight' => 54,
-    'cancel_weight'  => 70, #?  no deps, so
-    'fields' => {
-      'svcnum'     => 'Service',
-      'identifier' => 'Identifier',
-    },
+    'fields'          => \%fields,
+    'sorts'           => [ 'svcnum', 'serialnum', 'mac_addr', ],
+    'display_weight'  => 54,
+    'cancel_weight'   => 70, #?  no deps, so
   };
 }
 
@@ -93,13 +129,27 @@ sub check {
   my $self = shift;
 
   my $error = 
-    $self->ut_numbern('svcnum')
+       $self->ut_numbern('svcnum')
+    || $self->ut_foreign_key('modelnum', 'cable_model', 'modelnum')
+    || $self->ut_alpha('serialnum')
+    || $self->ut_mac_addr('mac_addr')
   ;
   return $error if $error;
 
   $self->SUPER::check;
 }
 
+=item cable_model
+
+Returns the cable_model object for this record.
+
+=cut
+
+sub cable_model {
+  my $self = shift;
+  qsearchs('cable_model', { 'modelnum'=>$self->modelnum } );
+}
+
 =back
 
 =head1 BUGS
diff --git a/FS/FS/svc_pbx.pm b/FS/FS/svc_pbx.pm
index 37ab174..8acd448 100644
--- a/FS/FS/svc_pbx.pm
+++ b/FS/FS/svc_pbx.pm
@@ -1,7 +1,8 @@
 package FS::svc_pbx;
+use base qw( FS::svc_External_Common );
 
 use strict;
-use base qw( FS::svc_External_Common );
+use Tie::IxHash;
 use FS::Record qw( qsearch qsearchs dbh );
 use FS::Conf;
 use FS::cust_svc;
@@ -79,6 +80,15 @@ points to.  You can ask the object for a copy with the I<hash> method.
 sub table { 'svc_pbx'; }
 
 sub table_info {
+
+  tie my %fields, 'Tie::IxHash',
+    'svcnum' => 'PBX',
+    'id'     => 'PBX/Tenant ID',
+    'title'  => 'Name',
+    'max_extensions' => 'Maximum number of User Extensions',
+    'max_simultaneous' => 'Maximum number of simultaneous users',
+  ;
+
   {
     'name' => 'PBX',
     'name_plural' => 'PBXs',
@@ -87,12 +97,7 @@ sub table_info {
     'sorts' => 'svcnum', # optional sort field (or arrayref of sort fields, main first)
     'display_weight' => 70,
     'cancel_weight'  => 90,
-    'fields' => {
-      'id'    => 'ID',
-      'title' => 'Name',
-      'max_extensions' => 'Maximum number of User Extensions',
-      'max_simultaneous' => 'Maximum number of simultaneous users',
-    },
+    'fields' => \%fields,
   };
 }
 
diff --git a/FS/MANIFEST b/FS/MANIFEST
index 76171ae..cd3ccd9 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -642,7 +642,7 @@ FS/log_context.pm
 t/log_context.t
 FS/svc_cable.pm
 t/svc_cable.t
-FS/cable_device.pm
-t/cable_device.t
 FS/h_svc_cable.pm
 t/h_svc_cable.t
+FS/cable_model.pm
+t/cable_model.t
diff --git a/httemplate/edit/svc_Common.html b/httemplate/edit/svc_Common.html
index 3da72d2..5949a4d 100644
--- a/httemplate/edit/svc_Common.html
+++ b/httemplate/edit/svc_Common.html
@@ -20,6 +20,7 @@ if ( UNIVERSAL::can("FS::$table", 'table_info') ) {
   $opt{'name'}   = "FS::$table"->table_info->{'name'};
 
   my $fields = "FS::$table"->table_info->{'fields'};
+
   my %labels = map { $_ => ( ref($fields->{$_})
                                ? $fields->{$_}{'label'}
 			       : $fields->{$_}
@@ -28,6 +29,17 @@ if ( UNIVERSAL::can("FS::$table", 'table_info') ) {
                keys %$fields;
   $opt{'labels'} = \%labels;
 
+  #transform FS::svc_* table_info's structure into one edit.html likes
+  delete $fields->{svcnum};
+  $opt{'fields'} = [ map { ref $fields->{$_}
+                             ? { field => $_,
+                                 %{ $fields->{$_} }
+                               }
+                             : $_
+                          } keys %$fields
+                   ];
+
+
 }
 
 </%init>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index 1c55c14..f9ad579 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -586,6 +586,7 @@ $config_misc{'Hardware types'} = [ $fsurl.'browse/hardware_class.html', 'Set up
 if ( $curuser->access_right('Configuration') ) {
   $config_misc{'RADIUS Groups'} = [ $fsurl.'browse/radius_group.html', 'Manage RADIUS groups' ];
   $config_misc{'RADIUS Clients'} = [ $fsurl.'browse/nas.html', 'Manage RADIUS clients' ];
+  $config_misc{'Cable modem models'} = [ $fsurl.'browse/cable_model.html', '' ];
 }
 
 tie my %config_menu, 'Tie::IxHash';

commit 5e5be65156dc03c73ac173af38329c187276d820
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue Sep 10 01:31:58 2013 -0700

    svc_cable service have a single serial / MAC / model, not one-to-many devices like svc_phone and svc_dsl, RT#22009

diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index b16d1ea..3d54f02 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -3700,26 +3700,25 @@ sub tables_hashref {
 
     'svc_cable' => {
       'columns' => [
-        'svcnum',         'int',     '',      '', '', '', 
-        #nothing so far...  there should be _something_ uniquely identifying
-        # each subscriber besides the device info...?
+        'svcnum',        'int',     '',      '', '', '', 
+        'modelnum',      'int', 'NULL',      '', '', '',
+        'serialnum', 'varchar', 'NULL', $char_d, '', '',
+        'mac_addr',  'varchar', 'NULL',      12, '', '', 
       ],
       'primary_key' => 'svcnum',
       'unique' => [],
       'index'  => [],
     },
 
-    'cable_device' => {
+    'cable_model' => {
       'columns' => [
-        'devicenum', 'serial',     '',      '', '', '',
-        'devicepart',   'int',     '',      '', '', '',
-        'svcnum',       'int',     '',      '', '', '', 
-        'mac_addr', 'varchar', 'NULL',      12, '', '', 
-        'serial',   'varchar', 'NULL', $char_d, '', '',
+        'modelnum',    'serial',     '',      '', '', '',
+        'model_name', 'varchar',     '', $char_d, '', '',
+        'disabled',      'char', 'NULL',       1, '', '', 
       ],
-      'primary_key' => 'devicenum',
-      'unique' => [ [ 'mac_addr' ], ],
-      'index'  => [ [ 'devicepart' ], [ 'svcnum' ], ],
+      'primary_key' => 'modelnum',
+      'unique' => [ [ 'model_name' ], ],
+      'index'  => [],
     },
 
     %{ tables_hashref_torrus() },

commit 6ca44d5ef4eef39d742838cc538b9da2e277c01e
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue Jul 23 21:51:12 2013 -0700

    svc_cable, RT#22009

diff --git a/httemplate/edit/cable_device.html b/httemplate/edit/cable_device.html
index eb91ad7..963bbf7 100644
--- a/httemplate/edit/cable_device.html
+++ b/httemplate/edit/cable_device.html
@@ -18,6 +18,9 @@
 			       { field => 'mac_addr',
 			         type => 'select-mac',
 			       },
+			       { field => 'serial',
+			         type  => 'text', #select-serial
+			       },
                                { 'field' => 'svcnum',
                                  'type'  => 'hidden',
                                },

commit 70999e67772fce6666191ec3fa95d9afb5f5b799
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue May 28 22:36:21 2013 -0700

    fix error viewing svc_broadband services, RT#23181, fallout from svc_cable, RT#22009

diff --git a/httemplate/view/elements/svc_devices.html b/httemplate/view/elements/svc_devices.html
index 745eabd..9458c92 100644
--- a/httemplate/view/elements/svc_devices.html
+++ b/httemplate/view/elements/svc_devices.html
@@ -94,6 +94,7 @@ if ( $table eq 'phone_device' || $table eq 'cable_device' ) {
 }
 
 my @devices = $svc_x->isa('FS::device_Common') ? $svc_x->device_objects()
-                                               : $svc_x->$table();
+                                               : $table ? $svc_x->$table()
+                                                        : ();
 
 </%init>

commit 7869fb435b3269128be4750556aa1a16bd3dfde3
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Wed May 15 01:25:49 2013 -0700

    add h_svc_cable for invoices, RT#22009

diff --git a/FS/FS/h_svc_cable.pm b/FS/FS/h_svc_cable.pm
new file mode 100644
index 0000000..cee2908
--- /dev/null
+++ b/FS/FS/h_svc_cable.pm
@@ -0,0 +1,32 @@
+package FS::h_svc_cable;
+
+use strict;
+use vars qw( @ISA );
+use FS::h_Common;
+use FS::svc_cable;
+
+ at ISA = qw( FS::h_Common FS::svc_cable );
+
+sub table { 'h_svc_cable' };
+
+=head1 NAME
+
+FS::h_svc_cable - Historical PBX objects
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+An FS::h_svc_cable object represents a historical cable subscriber.
+FS::h_svc_cable inherits from FS::h_Common and FS::svc_cable.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::h_Common>, L<FS::svc_cable>, L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/MANIFEST b/FS/MANIFEST
index a9fad0b..76171ae 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -644,3 +644,5 @@ FS/svc_cable.pm
 t/svc_cable.t
 FS/cable_device.pm
 t/cable_device.t
+FS/h_svc_cable.pm
+t/h_svc_cable.t
diff --git a/FS/t/h_svc_cable.t b/FS/t/h_svc_cable.t
new file mode 100644
index 0000000..7f9fad5
--- /dev/null
+++ b/FS/t/h_svc_cable.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::h_svc_cable;
+$loaded=1;
+print "ok 1\n";

commit a6168cf714d778bbaa9637c1d344b821dd153dd1
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue May 14 00:31:51 2013 -0700

    svc_cable (2.3 backport), RT#22009
    
    Conflicts:
    	FS/FS/Mason.pm

diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index baa4057..6f6b74d 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -315,6 +315,8 @@ if ( -e $addl_handler_use_file ) {
   use FS::agent_pkg_class;
   use FS::log;
   use FS::log_context;
+  use FS::svc_cable;
+  use FS::cable_device;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {

commit 34b6066b473530debe318fb48718dc2193cd6f85
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue May 14 00:30:41 2013 -0700

    svc_cable, RT#22009

diff --git a/FS/FS.pm b/FS/FS.pm
index a7164c5..2dcc796 100644
--- a/FS/FS.pm
+++ b/FS/FS.pm
@@ -206,6 +206,8 @@ L<FS::svc_cert> - Certificate service class
 
 L<FS::svc_dish> - Dish network service class
 
+L<FS::svc_cable> - Cable service class
+
 L<FS::inventory_class> - Inventory classes
 
 L<FS::inventory_item> - Inventory items

commit a97e5cbca3e7b9e7d59ecbe34057426b2ff7b34d
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue May 14 00:28:55 2013 -0700

    svc_cable (2.3 backport), RT#22009
    
    Conflicts:
    	FS/FS/access_right.pm
    	FS/MANIFEST
    	httemplate/docs/part_svc-table.html
    	httemplate/view/svc_phone.cgi

diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm
index 091e7af..6d0f96f 100644
--- a/FS/FS/AccessRight.pm
+++ b/FS/FS/AccessRight.pm
@@ -291,6 +291,7 @@ tie my %rights, 'Tie::IxHash',
     'Services: Wireless broadband services',
     'Services: Wireless broadband services: Advanced search',
     'Services: DSLs',
+    'Services: Cable subscribers',
     'Services: Dish services',
     'Services: Hardware',
     'Services: Hardware: Advanced search',
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index cfacbfc..b16d1ea 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -3698,6 +3698,30 @@ sub tables_hashref {
       'index' => [],
     },
 
+    'svc_cable' => {
+      'columns' => [
+        'svcnum',         'int',     '',      '', '', '', 
+        #nothing so far...  there should be _something_ uniquely identifying
+        # each subscriber besides the device info...?
+      ],
+      'primary_key' => 'svcnum',
+      'unique' => [],
+      'index'  => [],
+    },
+
+    'cable_device' => {
+      'columns' => [
+        'devicenum', 'serial',     '',      '', '', '',
+        'devicepart',   'int',     '',      '', '', '',
+        'svcnum',       'int',     '',      '', '', '', 
+        'mac_addr', 'varchar', 'NULL',      12, '', '', 
+        'serial',   'varchar', 'NULL', $char_d, '', '',
+      ],
+      'primary_key' => 'devicenum',
+      'unique' => [ [ 'mac_addr' ], ],
+      'index'  => [ [ 'devicepart' ], [ 'svcnum' ], ],
+    },
+
     %{ tables_hashref_torrus() },
 
     # tables of ours for doing torrus virtual port combining
diff --git a/FS/FS/access_right.pm b/FS/FS/access_right.pm
index a9d5de2..3e018ff 100644
--- a/FS/FS/access_right.pm
+++ b/FS/FS/access_right.pm
@@ -233,7 +233,8 @@ sub _upgrade_data { # class method
     'Financial reports' => [ 'Employees: Commission Report',
                              'Employees: Audit Report',
                            ],
-  ;
+    'Services: Accounts' => 'Services: Cable Subscribers',
+;
 
   foreach my $old_acl ( keys %onetime ) {
 
diff --git a/FS/FS/cable_device.pm b/FS/FS/cable_device.pm
new file mode 100644
index 0000000..1a0f1b9
--- /dev/null
+++ b/FS/FS/cable_device.pm
@@ -0,0 +1,140 @@
+package FS::cable_device;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearchs ); # qsearch );
+use FS::part_device;
+use FS::svc_cable;
+
+=head1 NAME
+
+FS::cable_device - Object methods for cable_device records
+
+=head1 SYNOPSIS
+
+  use FS::cable_device;
+
+  $record = new FS::cable_device \%hash;
+  $record = new FS::cable_device { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cable_device object represents a specific customer cable modem.
+FS::cable_device inherits from FS::Record.  The following fields are currently
+supported:
+
+=over 4
+
+=item devicenum
+
+primary key
+
+=item devicepart
+
+devicepart
+
+=item svcnum
+
+svcnum
+
+=item mac_addr
+
+mac_addr
+
+=item serial
+
+serial
+
+
+=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
+
+sub table { 'cable_device'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid record.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $mac = $self->mac_addr;
+  $mac =~ s/\s+//g;
+  $mac =~ s/://g;
+  $self->mac_addr($mac);
+
+  my $error = 
+    $self->ut_numbern('devicenum')
+    || $self->ut_number('devicepart')
+    || $self->ut_foreign_key('devicepart', 'part_device', 'devicepart')
+    || $self->ut_foreign_key('svcnum', 'svc_cable', 'svcnum' ) #cust_svc?
+    || $self->ut_hexn('mac_addr')
+    || $self->ut_textn('serial')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=item part_device
+
+Returns the device type record (see L<FS::part_device>) associated with this
+customer device.
+
+=cut
+
+sub part_device {
+  my $self = shift;
+  qsearchs( 'part_device', { 'devicepart' => $self->devicepart } );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/device_Common.pm b/FS/FS/device_Common.pm
new file mode 100644
index 0000000..ac00b76
--- /dev/null
+++ b/FS/FS/device_Common.pm
@@ -0,0 +1,78 @@
+package FS::device_Common;
+
+use strict;
+use NEXT;
+use FS::Record qw( qsearch dbh ); # qsearchs );
+
+=head1 NAME
+
+FS::device_Common - Base class for svc_X classes which have associated X_devices
+
+=head1 SYNOPSIS
+
+  package FS::svc_newservice
+  use base qw( FS::device_Common FS::svc_Common );
+
+=head1 DESCRIPTION
+
+=cut
+
+sub _device_table {
+  my $self = shift;
+  ( my $device_table = $self->table ) =~ s/^svc_//;
+  $device_table.'_device';
+}
+
+sub device_table {
+  my $self = shift;
+  my $device_table = $self->_device_table;
+  eval "use FS::$device_table;";
+  die $@ if $@;
+  $device_table;
+}
+
+sub device_objects {
+  my $self = shift;
+  qsearch($self->device_table, { 'svcnum' => $self->svcnum } );
+}
+
+sub delete {
+  my $self = shift;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  foreach my $device ( $self->device_objects ) {
+    my $error = $device->delete;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  my $error = $self->NEXT::delete;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  '';
+
+}
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+=cut
+
+1;
diff --git a/FS/FS/svc_cable.pm b/FS/FS/svc_cable.pm
new file mode 100644
index 0000000..f588f43
--- /dev/null
+++ b/FS/FS/svc_cable.pm
@@ -0,0 +1,114 @@
+package FS::svc_cable;
+use base qw( FS::device_Common FS::svc_Common );
+
+use strict;
+use base qw( FS::Record );
+use FS::Record; # qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::svc_cable - Object methods for svc_cable records
+
+=head1 SYNOPSIS
+
+  use FS::svc_cable;
+
+  $record = new FS::svc_cable \%hash;
+  $record = new FS::svc_cable { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::svc_cable object represents a cable subscriber.  FS::svc_cable inherits
+from FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item svcnum
+
+primary key
+
+=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
+
+sub table { 'svc_cable'; }
+
+sub table_info {
+  {
+    'name' => 'Cable Subscriber',
+    #'name_plural' => '', #optional,
+    #'longname_plural' => '', #optional
+    'sorts' => [ 'svcnum', ], #, 'serviceid' ], # optional sort field (or arrayref of sort fields, main first)
+    'display_weight' => 54,
+    'cancel_weight'  => 70, #?  no deps, so
+    'fields' => {
+      'svcnum'     => 'Service',
+      'identifier' => 'Identifier',
+    },
+  };
+}
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid record.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('svcnum')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/MANIFEST b/FS/MANIFEST
index 4e7a56c..a9fad0b 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -640,3 +640,7 @@ FS/log.pm
 t/log.t
 FS/log_context.pm
 t/log_context.t
+FS/svc_cable.pm
+t/svc_cable.t
+FS/cable_device.pm
+t/cable_device.t
diff --git a/FS/t/cable_device.t b/FS/t/cable_device.t
new file mode 100644
index 0000000..016d2c5
--- /dev/null
+++ b/FS/t/cable_device.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cable_device;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_cable.t b/FS/t/svc_cable.t
new file mode 100644
index 0000000..5057659
--- /dev/null
+++ b/FS/t/svc_cable.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_cable;
+$loaded=1;
+print "ok 1\n";
diff --git a/httemplate/edit/cable_device.html b/httemplate/edit/cable_device.html
new file mode 100644
index 0000000..eb91ad7
--- /dev/null
+++ b/httemplate/edit/cable_device.html
@@ -0,0 +1,114 @@
+<% include( 'elements/edit.html',
+                 'name'   => 'Cable device',
+                 'table'  => 'cable_device',
+                 'labels' => { 
+                               'devicenum'  => 'Device',
+                               'devicepart' => 'Device type',
+                               'mac_addr'   => 'MAC address',
+                               'serial'     => 'Serial number',
+                             },
+                 'fields' => [ { 'field'    => 'devicepart',
+                                 'type'     => 'select-table',
+                                 'table'    => 'part_device',
+                                 'name_col' => 'devicename',
+				 'onchange' => 'devicepart_changed',
+                                 'empty_label' =>'Select device type',
+                                 #'hashref'        =>{ disabled => '' },
+                               },
+			       { field => 'mac_addr',
+			         type => 'select-mac',
+			       },
+                               { 'field' => 'svcnum',
+                                 'type'  => 'hidden',
+                               },
+                             ],
+                 'menubar' => [], #disable viewall
+                 #'viewall_dir' => 'browse',
+                 'new_callback' => sub {
+                                     my( $cgi, $object ) = @_;
+                                     $object->svcnum( $cgi->param('svcnum') );
+                                   },
+		 'html_foot' => $html_foot,
+           )
+%>
+<%init>
+
+#bad: pretty much entirely false laziness w/phone_device, except for labels and
+# the serial field
+
+my @deviceparts_with_inventory =
+  map $_->devicepart,
+    qsearch({ 'table'     => 'part_device',
+              'extra_sql' => 'WHERE inventory_classnum IS NOT NULL',
+           });
+
+my $html_foot = sub {
+    my $js = "
+<SCRIPT TYPE=\"text/javascript\">
+
+  function opt(what,value,text) {
+    var optionName = new Option(text, value, false, false);
+    var length = what.length;
+    what.options[length] = optionName;
+  }
+
+    function devicepart_changed(what){
+	
+	var macsel = document.getElementById('sel_mac_addr');
+	var mac = document.getElementById('mac_addr');
+	
+	function update_macs(macs) {
+	    for ( var i = macsel.length; i >= 0; i-- )
+	      macsel.options[i] = null;
+	    
+	    var macArray = eval('(' + macs + ')' );
+	    if(macArray.length == 0) 
+		opt(macsel,'','No MAC addresses found in inventory for this device type');
+	    else
+		opt(macsel,'','Select MAC address');
+
+	    for ( var i = 0; i < macArray.length; i++ ) {
+		opt(macsel,macArray[i],macArray[i]);
+	    }
+
+	}
+
+	var devicepart = what.options[what.selectedIndex].value;
+
+	var deviceparts_with_inventory = new Array(";
+$js .= join(',', map qq("$_"), @deviceparts_with_inventory);
+$js .= ");
+
+	var hasInventory = false;
+	for ( i = 0; i < deviceparts_with_inventory.length; i++ ) {
+	    if ( deviceparts_with_inventory[i] == devicepart ) 
+		hasInventory = true;
+	}
+	
+
+	if(hasInventory) { // do the AJAX thing, disable text field
+	    macsel.style.display = 'inline';
+	    mac.style.display = 'none';
+	    mac.value = '';
+	    get_macs( devicepart, update_macs );
+	} else { // clear & display text field only, clear/hide select
+	    mac.style.display = 'inline';
+	    macsel.style.display = 'none';
+	    macsel.selectedIndex = 0;
+	}
+
+    }
+
+    devicepart_changed(document.getElementById('devicepart'));
+</SCRIPT>";
+
+  $js;
+};
+
+# :/  needs agent-virt so you can't futz with arbitrary devices
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+
+</%init>
diff --git a/httemplate/edit/process/cable_device.html b/httemplate/edit/process/cable_device.html
new file mode 100644
index 0000000..97b4f81
--- /dev/null
+++ b/httemplate/edit/process/cable_device.html
@@ -0,0 +1,23 @@
+<% include( 'elements/process.html',
+               'table'    => 'cable_device',
+               'redirect' => sub {
+                 my( $cgi, $cable_device ) = @_;
+                 #popurl(3).'view/svc_cable.html?'.
+                 popurl(3).'view/svc_Common.html?svcdb=svc_cable;'.
+                   'svcnum='. $cable_device->svcnum.
+                   ';devicenum=';
+               },
+           )
+%>
+<%init>
+
+if($cgi->param('sel_mac_addr') && !$cgi->param('mac_addr')) {
+    $cgi->param('mac_addr',$cgi->param('sel_mac_addr'));
+}
+
+# :/  needs agent-virt so you can't futz with arbitrary devices
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+</%init>
diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html
index 2d1201b..e5cf9f6 100644
--- a/httemplate/view/elements/svc_Common.html
+++ b/httemplate/view/elements/svc_Common.html
@@ -106,6 +106,11 @@ function areyousure(href) {
 
 <BR>
 
+<& svc_devices.html,
+     'svc_x' => $svc_x,
+     'table' => $svc_x->device_table,
+&>
+
 % if ( defined($opt{'html_foot'}) ) {
 
   <% ref($opt{'html_foot'})
diff --git a/httemplate/view/elements/svc_devices.html b/httemplate/view/elements/svc_devices.html
index 38c6d09..745eabd 100644
--- a/httemplate/view/elements/svc_devices.html
+++ b/httemplate/view/elements/svc_devices.html
@@ -30,7 +30,7 @@
 
      <& /elements/table-grid.html &>
        <TR>
-%        if ( $table eq 'phone_device' ) {
+%        if ( $table eq 'phone_device' || $table eq 'cable_device' ) {
            <TH CLASS="grid" BGCOLOR="#cccccc">Type</TH>
 %        }
          <TH CLASS="grid" BGCOLOR="#cccccc">MAC Addr</TH>
@@ -58,7 +58,8 @@
 %          if $device->can('export_links');
 
         <TR>
-%         if ( $table eq 'phone_device' ) { #$devices->can('part_device')
+%              #$devices->can('part_device')
+%         if ( $table eq 'phone_device' || $svc_x->isa('FS::device_Common') ) {
             <% $td %><% $device->part_device->devicename |h %></TD>
 %         }
           <% $td %><% $device->mac_addr %></TD>
@@ -84,7 +85,7 @@ my $table = $opt{'table'}; #part_device, dsl_device
 my $svc_x = $opt{'svc_x'};
 
 my $num_part_device = 0;
-if ( $table eq 'phone_device' ) {
+if ( $table eq 'phone_device' || $table eq 'cable_device' ) {
   my $sth = dbh->prepare("SELECT COUNT(*) FROM part_device")
                             #WHERE disabled = '' OR disabled IS NULL;");
     or die dbh->errstr;
@@ -92,6 +93,7 @@ if ( $table eq 'phone_device' ) {
   $num_part_device = $sth->fetchrow_arrayref->[0];
 }
 
-my @devices = $svc_x->$table();
+my @devices = $svc_x->isa('FS::device_Common') ? $svc_x->device_objects()
+                                               : $svc_x->$table();
 
 </%init>

-----------------------------------------------------------------------

Summary of changes:
 FS/FS.pm                                       |    4 +
 FS/FS/AccessRight.pm                           |    1 +
 FS/FS/Mason.pm                                 |    2 +
 FS/FS/Schema.pm                                |   23 ++++
 FS/FS/access_right.pm                          |    3 +-
 FS/FS/cable_model.pm                           |  109 ++++++++++++++++
 FS/FS/device_Common.pm                         |   78 +++++++++++
 FS/FS/h_svc_cable.pm                           |   32 +++++
 FS/FS/svc_broadband.pm                         |   10 +-
 FS/FS/svc_cable.pm                             |  164 ++++++++++++++++++++++++
 FS/FS/svc_pbx.pm                               |   19 ++-
 FS/MANIFEST                                    |    6 +
 FS/t/cable_model.t                             |    5 +
 FS/t/h_svc_cable.t                             |    5 +
 FS/t/svc_cable.t                               |    5 +
 httemplate/browse/cable_model.html             |   33 +++++
 httemplate/edit/cable_model.html               |   20 +++
 httemplate/edit/part_svc.cgi                   |    4 +-
 httemplate/edit/process/cable_model.html       |   10 ++
 httemplate/edit/svc_Common.html                |   12 ++
 httemplate/elements/menu.html                  |    1 +
 httemplate/elements/select-cable_model.html    |    7 +
 httemplate/elements/tr-select-cable_model.html |   12 ++
 httemplate/view/elements/svc_Common.html       |    5 +
 httemplate/view/elements/svc_devices.html      |   11 +-
 httemplate/view/svc_Common.html                |   11 ++
 26 files changed, 575 insertions(+), 17 deletions(-)
 create mode 100644 FS/FS/cable_model.pm
 create mode 100644 FS/FS/device_Common.pm
 create mode 100644 FS/FS/h_svc_cable.pm
 create mode 100644 FS/FS/svc_cable.pm
 create mode 100644 FS/t/cable_model.t
 create mode 100644 FS/t/h_svc_cable.t
 create mode 100644 FS/t/svc_cable.t
 create mode 100644 httemplate/browse/cable_model.html
 create mode 100644 httemplate/edit/cable_model.html
 create mode 100644 httemplate/edit/process/cable_model.html
 create mode 100644 httemplate/elements/select-cable_model.html
 create mode 100644 httemplate/elements/tr-select-cable_model.html




More information about the freeside-commits mailing list