svc_pbx devices, for RT#24968
authorIvan Kohler <ivan@freeside.biz>
Fri, 24 Jan 2014 08:39:40 +0000 (00:39 -0800)
committerIvan Kohler <ivan@freeside.biz>
Fri, 24 Jan 2014 08:39:40 +0000 (00:39 -0800)
23 files changed:
FS/FS.pm
FS/FS/MAC_Mixin.pm [new file with mode: 0644]
FS/FS/Mason.pm
FS/FS/Schema.pm
FS/FS/dsl_device.pm
FS/FS/extension_device.pm [new file with mode: 0644]
FS/FS/pbx_device.pm [new file with mode: 0644]
FS/FS/phone_device.pm
FS/FS/svc_External_Common.pm
FS/FS/svc_MAC_Mixin.pm [deleted file]
FS/FS/svc_broadband.pm
FS/FS/svc_cable.pm
FS/FS/svc_pbx.pm
FS/FS/svc_video.pm
FS/MANIFEST
FS/t/extension_device.t [new file with mode: 0644]
FS/t/pbx_device.t [new file with mode: 0644]
httemplate/edit/elements/device_Common.html [new file with mode: 0644]
httemplate/edit/pbx_device.html [new file with mode: 0644]
httemplate/edit/process/elements/device_Common.html [new file with mode: 0644]
httemplate/edit/process/pbx_device.html [new file with mode: 0644]
httemplate/misc/delete-pbx_device.html [new file with mode: 0755]
httemplate/view/elements/svc_devices.html

index 11d8b6e..c19d2a9 100644 (file)
--- a/FS/FS.pm
+++ b/FS/FS.pm
@@ -206,6 +206,8 @@ L<FS::svc_pbx> - PBX service class
 
 L<FS::pbx_extension> - PBX extension class
 
+L<FS::pbx_device> - PBX device class
+
 L<FS::svc_cert> - Certificate service class
 
 L<FS::svc_dish> - Dish network service class
diff --git a/FS/FS/MAC_Mixin.pm b/FS/FS/MAC_Mixin.pm
new file mode 100644 (file)
index 0000000..8715995
--- /dev/null
@@ -0,0 +1,65 @@
+package FS::MAC_Mixin;
+
+use strict;
+#use FS::Record qw(qsearch);
+#use FS::Conf;
+# careful about importing anything here--it will end up in a LOT of 
+# namespaces
+
+#use vars qw(@subclasses $DEBUG $conf);
+
+#$DEBUG = 0;
+
+# any subclass that can have MAC addresses needs to be added here
+#@subclasses = (qw(FS::svc_broadband FS::svc_cable));
+
+#sub conf {
+#  $conf ||= FS::Conf->new;
+#}
+
+=head1 NAME
+
+FS::MAC_Mixin - Mixin class for objects that have MAC addresses assigned.
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=head1 METHODS
+
+=over 4
+
+=item mac_addr_pretty
+
+=cut
+
+sub mac_addr_pretty {
+  my $self = shift;
+  $self->mac_addr_formatted('U',':');
+}
+
+=item mac_addr_formatted CASE DELIMITER
+
+Format the MAC address (for use by exports).  If CASE starts with "l"
+(for "lowercase"), it's returned in lowercase.  DELIMITER is inserted
+between octets.
+
+=cut
+
+sub mac_addr_formatted {
+  my $self = shift;
+  my ($case, $delim) = @_;
+  my $addr = $self->mac_addr;
+  $addr = lc($addr) if $case =~ /^l/i;
+  join( $delim || '', $addr =~ /../g );
+}
+
+=back
+
+=head1 BUGS
+
+=cut
+
+1; 
index 7d78376..a4eac45 100644 (file)
@@ -369,6 +369,8 @@ if ( -e $addl_handler_use_file ) {
   use FS::part_pkg_usageprice;
   use FS::cust_pkg_usageprice;
   use FS::pbx_extension;
+  use FS::pbx_device;
+  use FS::extension_device;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {
index 2300c07..0447892 100644 (file)
@@ -5676,6 +5676,45 @@ sub tables_hashref {
                         ],
     },
 
+    'pbx_device' => {
+      'columns' => [
+        'devicenum', 'serial',     '', '', '', '',
+        'devicepart',   'int',     '', '', '', '',
+        'svcnum',       'int',     '', '', '', '', 
+        'mac_addr', 'varchar', 'NULL', 12, '', '', 
+      ],
+      'primary_key'  => 'devicenum',
+      'unique'       => [ [ 'mac_addr' ], ],
+      'index'        => [ [ 'devicepart' ], [ 'svcnum' ], ],
+      'foreign_keys' => [
+                          { columns    => [ 'devicepart' ],
+                            table      => 'part_device',
+                          },
+                          { columns    => [ 'svcnum' ],
+                            table      => 'svc_pbx',
+                          },
+                        ],
+    },
+
+    'extension_device' => {
+      'columns' => [
+        'extensiondevicenum', 'serial', '', '', '', '',
+        'extensionnum',          'int', '', '', '', '',
+        'devicenum',             'int', '', '', '', '',
+      ],
+      'primary_key'  => 'extensiondevicenum',
+      'unique'       => [ [ 'extensionnum', 'devicenum' ] ],
+      'index'        => [],#both?  which way do we need to query?
+      'foreign_keys' => [
+                          { columns  => [ 'extensionnum' ],
+                            table    => 'pbx_extension',
+                          },
+                          { columns  => [ 'devicenum' ],
+                            table    => 'pbx_device',
+                          },
+                        ],
+    },
+
     'svc_mailinglist' => { #svc_group?
       'columns' => [
         'svcnum',            'int',     '',            '', '', '', 
index 39e8c34..1e86d44 100644 (file)
@@ -1,5 +1,5 @@
 package FS::dsl_device;
-use base qw( FS::Record );
+use base qw( FS::MAC_Mixin FS::Record );
 
 use strict;
 
diff --git a/FS/FS/extension_device.pm b/FS/FS/extension_device.pm
new file mode 100644 (file)
index 0000000..23d881e
--- /dev/null
@@ -0,0 +1,110 @@
+package FS::extension_device;
+use base qw( FS::Record );
+
+use strict;
+#use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::extension_device - Object methods for extension_device records
+
+=head1 SYNOPSIS
+
+  use FS::extension_device;
+
+  $record = new FS::extension_device \%hash;
+  $record = new FS::extension_device { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::extension_device object represents a PBX extension association with a 
+specific PBX device (SIP phone or ATA).  FS::extension_device inherits from
+FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item extensiondevicenum
+
+primary key
+
+=item extensionnum
+
+extensionnum
+
+=item devicenum
+
+devicenum
+
+
+=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 { 'extension_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 $error = 
+    $self->ut_numbern('extensiondevicenum')
+    || $self->ut_foreign_keyn('extensionnum', 'pbx_extension', 'extensionnum')
+    || $self->ut_foreign_keyn('devicenum', 'pbx_device', 'devicenum')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/pbx_device.pm b/FS/FS/pbx_device.pm
new file mode 100644 (file)
index 0000000..ec1c3b9
--- /dev/null
@@ -0,0 +1,114 @@
+package FS::pbx_device;
+use base qw( FS::MAC_Mixin FS::Record );
+
+use strict;
+#use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::pbx_device - Object methods for pbx_device records
+
+=head1 SYNOPSIS
+
+  use FS::pbx_device;
+
+  $record = new FS::pbx_device \%hash;
+  $record = new FS::pbx_device { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::pbx_device object represents a specific customer phone device, such
+as a SIP phone or ATA.  FS::pbx_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
+
+
+=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 { 'pbx_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 $error = 
+    $self->ut_numbern('devicenum')
+    || $self->ut_foreign_key('devicepart', 'part_device', 'devicepart')
+    || $self->ut_foreign_key('svcnum', 'svc_pbx', 'svcnum')
+    || $self->ut_mac_addr('mac_addr')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
index d5f80a2..b891bb2 100644 (file)
@@ -1,5 +1,5 @@
 package FS::phone_device;
-use base qw( FS::Record );
+use base qw( FS::MAC_Mixin FS::Record );
 
 use strict;
 use Scalar::Util qw( blessed );
index a5805aa..32fe7f3 100644 (file)
@@ -8,28 +8,12 @@ use FS::svc_Common;
 
 =head1 NAME
 
-FS::svc_external - Object methods for svc_external records
+FS::svc_External_Common - Base class for svc_X classes which track external databases
 
 =head1 SYNOPSIS
 
-  use FS::svc_external;
-
-  $record = new FS::svc_external \%hash;
-  $record = new FS::svc_external { 'column' => 'value' };
-
-  $error = $record->insert;
-
-  $error = $new_record->replace($old_record);
-
-  $error = $record->delete;
-
-  $error = $record->check;
-
-  $error = $record->suspend;
-
-  $error = $record->unsuspend;
-
-  $error = $record->cancel;
+  package FS::svc_newservice;
+  use base qw( FS::svc_External_Common );
 
 =head1 DESCRIPTION
 
diff --git a/FS/FS/svc_MAC_Mixin.pm b/FS/FS/svc_MAC_Mixin.pm
deleted file mode 100644 (file)
index 737a8e8..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-package FS::svc_MAC_Mixin;
-
-use strict;
-#use FS::Record qw(qsearch);
-#use FS::Conf;
-# careful about importing anything here--it will end up in a LOT of 
-# namespaces
-
-#use vars qw(@subclasses $DEBUG $conf);
-
-#$DEBUG = 0;
-
-# any subclass that can have MAC addresses needs to be added here
-#@subclasses = (qw(FS::svc_broadband FS::svc_cable));
-
-#sub conf {
-#  $conf ||= FS::Conf->new;
-#}
-
-=head1 NAME
-
-FS::MAC_Mixin - Mixin class for objects that have MAC addresses assigned.
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
-
-=head1 METHODS
-
-=head1 METHODS
-
-=over 4
-
-=item mac_addr_pretty
-
-=cut
-
-sub mac_addr_pretty {
-  my $self = shift;
-  $self->mac_addr_formatted('U',':');
-}
-
-=item mac_addr_formatted CASE DELIMITER
-
-Format the MAC address (for use by exports).  If CASE starts with "l"
-(for "lowercase"), it's returned in lowercase.  DELIMITER is inserted
-between octets.
-
-=cut
-
-sub mac_addr_formatted {
-  my $self = shift;
-  my ($case, $delim) = @_;
-  my $addr = $self->mac_addr;
-  $addr = lc($addr) if $case =~ /^l/i;
-  join( $delim || '', $addr =~ /../g );
-}
-
-=back
-
-=head1 BUGS
-
-=cut
-
-1; 
index b9c89ce..ce50a0c 100755 (executable)
@@ -3,7 +3,7 @@ use base qw(
   FS::svc_Radius_Mixin
   FS::svc_Tower_Mixin
   FS::svc_IP_Mixin 
-  FS::svc_MAC_Mixin
+  FS::MAC_Mixin
   FS::svc_Common
   );
 
index 3a1dc5a..12a1dbb 100644 (file)
@@ -1,5 +1,5 @@
 package FS::svc_cable;
-use base qw( FS::svc_MAC_Mixin
+use base qw( FS::MAC_Mixin
              FS::svc_Common
            ); #FS::device_Common
 
index 7899621..d35b3a2 100644 (file)
@@ -1,5 +1,5 @@
 package FS::svc_pbx;
-use base qw( FS::o2m_Common FS::svc_External_Common );
+use base qw( FS::o2m_Common FS::device_Common FS::svc_External_Common );
 
 use strict;
 use Tie::IxHash;
index a6ff136..999b87c 100644 (file)
@@ -1,5 +1,5 @@
 package FS::svc_video;
-use base qw( FS::svc_MAC_Mixin FS::svc_Common );
+use base qw( FS::MAC_Mixin FS::svc_Common );
 
 use strict;
 use Tie::IxHash;
index f2f1e39..bedc26c 100644 (file)
@@ -751,3 +751,7 @@ FS/cust_pkg_usageprice.pm
 t/cust_pkg_usageprice.t
 FS/pbx_extension.pm
 t/pbx_extension.t
+FS/pbx_device.pm
+t/pbx_device.t
+FS/extension_device.pm
+t/extension_device.t
diff --git a/FS/t/extension_device.t b/FS/t/extension_device.t
new file mode 100644 (file)
index 0000000..5927a17
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::extension_device;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/pbx_device.t b/FS/t/pbx_device.t
new file mode 100644 (file)
index 0000000..9cd5499
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::pbx_device;
+$loaded=1;
+print "ok 1\n";
diff --git a/httemplate/edit/elements/device_Common.html b/httemplate/edit/elements/device_Common.html
new file mode 100644 (file)
index 0000000..ab81357
--- /dev/null
@@ -0,0 +1,110 @@
+<& edit.html,
+     'labels' => { 
+                   'devicenum'  => 'Device',
+                   'devicepart' => 'Device type',
+                   'mac_addr'   => 'MAC address',
+                 },
+     '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,
+     %opt,
+&>
+<%init>
+
+my %opt = @_;
+
+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/pbx_device.html b/httemplate/edit/pbx_device.html
new file mode 100644 (file)
index 0000000..457ec71
--- /dev/null
@@ -0,0 +1,4 @@
+<& elements/device_Common.html,
+     'name'  => 'PBX device',
+     'table' => 'pbx_device',
+&>
diff --git a/httemplate/edit/process/elements/device_Common.html b/httemplate/edit/process/elements/device_Common.html
new file mode 100644 (file)
index 0000000..7b6f2cd
--- /dev/null
@@ -0,0 +1,27 @@
+<& process.html,
+     'redirect' => sub {
+       my( $cgi, $X_device ) = @_;
+       popurl(3)."view/$svc_table.cgi?".
+         'svcnum='. $X_device->svcnum.
+         ';devicenum=';
+     },
+     %opt,
+&>
+<%init>
+
+my %opt = @_;
+
+warn my $table = $opt{table};
+( my $svc_table = $table ) =~ s/_device//;
+$svc_table = "svc_$svc_table";
+
+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/edit/process/pbx_device.html b/httemplate/edit/process/pbx_device.html
new file mode 100644 (file)
index 0000000..664a9e7
--- /dev/null
@@ -0,0 +1,3 @@
+<& elements/device_Common.html,
+     'table' => 'pbx_device',
+&>
diff --git a/httemplate/misc/delete-pbx_device.html b/httemplate/misc/delete-pbx_device.html
new file mode 100755 (executable)
index 0000000..7540d2f
--- /dev/null
@@ -0,0 +1,23 @@
+% if ( $error ) {
+%   errorpage($error);
+% } else {
+<% $cgi->redirect($p. "view/svc_pbx.cgi?". $svcnum) %>
+% }
+<%init>
+
+# :/  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?
+
+#untaint devicenum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal devicenum";
+my $devicenum = $1;
+
+my $pbx_device = qsearchs('pbx_device', { 'devicenum' => $devicenum } );
+my $svcnum = $pbx_device->svcnum;
+
+my $error = $pbx_device->delete;
+
+</%init>
index 9458c92..c9f5eda 100644 (file)
@@ -15,7 +15,7 @@
 %if ( @devices || $num_part_device || $table eq 'dsl_device' ) {
 %  my $svcnum = $svc_x->svcnum;
 
-   Devices
+   <FONT CLASS="fsinnerbox-title">Devices</FONT>
    (<A HREF="<%$p%>edit/<%$table%>.html?svcnum=<%$svcnum%>">Add device</A>)
    <BR>
 
@@ -30,7 +30,7 @@
 
      <& /elements/table-grid.html &>
        <TR>
-%        if ( $table eq 'phone_device' || $table eq 'cable_device' ) {
+%        if ( $table ne 'dsl_device' ) { # ( $table eq 'phone_device' || $table eq 'cable_device' || $table eq 'pbx_device' ) {
            <TH CLASS="grid" BGCOLOR="#cccccc">Type</TH>
 %        }
          <TH CLASS="grid" BGCOLOR="#cccccc">MAC Addr</TH>
@@ -62,7 +62,7 @@
 %         if ( $table eq 'phone_device' || $svc_x->isa('FS::device_Common') ) {
             <% $td %><% $device->part_device->devicename |h %></TD>
 %         }
-          <% $td %><% $device->mac_addr %></TD>
+          <% $td %><% $device->mac_addr_pretty %></TD>
           <% $td %><% $export_links %></TD>
           <% $td %>(
 %           unless ( $opt{'no_edit'} ) {
@@ -85,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' || $table eq 'cable_device' ) {
+if ( $table ne 'dsl_device' ) { # ( $table eq 'phone_device' || $table eq 'cable_device' || $table eq 'pbx_device' ) {
   my $sth = dbh->prepare("SELECT COUNT(*) FROM part_device")
                             #WHERE disabled = '' OR disabled IS NULL;");
     or die dbh->errstr;