phone devices (for netsapiens integration), RT#5226
authorivan <ivan>
Tue, 30 Jun 2009 01:42:56 +0000 (01:42 +0000)
committerivan <ivan>
Tue, 30 Jun 2009 01:42:56 +0000 (01:42 +0000)
19 files changed:
FS/FS.pm
FS/FS/Mason.pm
FS/FS/Schema.pm
FS/FS/part_device.pm [new file with mode: 0644]
FS/FS/phone_device.pm [new file with mode: 0644]
FS/FS/svc_phone.pm
FS/MANIFEST
FS/t/part_device.t [new file with mode: 0644]
FS/t/phone_device.t [new file with mode: 0644]
httemplate/browse/part_device.html [new file with mode: 0644]
httemplate/edit/part_device.html [new file with mode: 0644]
httemplate/edit/phone_device.html [new file with mode: 0644]
httemplate/edit/process/part_device.html [new file with mode: 0644]
httemplate/edit/process/phone_device.html [new file with mode: 0644]
httemplate/elements/menu.html
httemplate/misc/delete-phone_device.html [new file with mode: 0755]
httemplate/misc/part_device-import.html [new file with mode: 0644]
httemplate/misc/process/part_device-import.html [new file with mode: 0644]
httemplate/view/svc_phone.cgi

index c314873..e9efcc1 100644 (file)
--- a/FS/FS.pm
+++ b/FS/FS.pm
@@ -138,6 +138,10 @@ L<FS::part_virtual_field> - Broadband virtual field class
 
 L<FS::svc_phone> - Phone service class
 
+L<FS::phone_device> - Phone device class
+
+L<FS::part_device> - Device definition class
+
 L<FS::phone_avail> - Phone number availability cache
 
 L<FS::cdr> - Call Detail Record class
index 2a22bfd..cce2dbf 100644 (file)
@@ -174,6 +174,8 @@ Initializes the Mason environment, loads all Freeside and RT libraries, etc.
   use FS::access_right;
   use FS::AccessRight;
   use FS::svc_phone;
+  use FS::phone_device;
+  use FS::part_device;
   use FS::reason_type;
   use FS::reason;
   use FS::cust_main_note;
index 61cd17e..4351f28 100644 (file)
@@ -2306,6 +2306,29 @@ sub tables_hashref {
       'index'  => [ [ 'countrycode', 'phonenum' ] ],
     },
 
+    'phone_device' => {
+      'columns' => [
+        'devicenum', 'serial',     '', '', '', '',
+        'devicepart',   'int',     '', '', '', '',
+        'svcnum',       'int',     '', '', '', '', 
+        'mac_addr', 'varchar', 'NULL', 12, '', '', 
+      ],
+      'primary_key' => 'devicenum',
+      'unique' => [ [ 'mac_addr' ], ],
+      'index'  => [ [ 'devicepart' ], [ 'svcnum' ], ],
+    },
+
+    'part_device' => {
+      'columns' => [
+        'devicepart', 'serial',  '',      '', '', '',
+        'devicename', 'varchar', '', $char_d, '', '',
+        #'classnum', #tie to an inventory class?
+      ],
+      'primary_key' => 'devicepart',
+      'unique' => [ [ 'devicename' ] ], #?
+      'index'  => [],
+    },
+
     'phone_avail' => {
       'columns' => [
         'availnum',    'serial',      '',      '', '', '', 
diff --git a/FS/FS/part_device.pm b/FS/FS/part_device.pm
new file mode 100644 (file)
index 0000000..79a534a
--- /dev/null
@@ -0,0 +1,134 @@
+package FS::part_device;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record; # qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::part_device - Object methods for part_device records
+
+=head1 SYNOPSIS
+
+  use FS::part_device;
+
+  $record = new FS::part_device \%hash;
+  $record = new FS::part_device { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_device object represents a phone device definition. FS::part_device
+inherits from FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item devicepart
+
+primary key
+
+=item devicename
+
+devicename
+
+
+=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 { 'part_device'; }
+
+=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 record.  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('devicepart')
+    || $self->ut_text('devicename')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+sub process_batch_import {
+  my $job = shift;
+
+  my $opt = { 'table'   => 'part_device',
+              'params'  => [],
+              'formats' => { 'default' => [ 'devicename' ] },
+              'default_csv' => 1,
+            };
+
+  FS::Record::process_batch_import( $job, $opt, @_ );
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/phone_device.pm b/FS/FS/phone_device.pm
new file mode 100644 (file)
index 0000000..a7097a1
--- /dev/null
@@ -0,0 +1,151 @@
+package FS::phone_device;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearchs ); # qsearch );
+use FS::part_device;
+use FS::svc_phone;
+
+=head1 NAME
+
+FS::phone_device - Object methods for phone_device records
+
+=head1 SYNOPSIS
+
+  use FS::phone_device;
+
+  $record = new FS::phone_device \%hash;
+  $record = new FS::phone_device { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::phone_device object represents a specific customer phone device, such as
+a SIP phone or ATA.  FS::phone_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
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'phone_device'; }
+
+=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 record.  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 $mac = $self->mac_addr;
+  $mac =~ s/\s+//g;
+  $mac =~ s/://g;
+  $self->mac_addr($mac);
+
+  my $error = 
+    $self->ut_numbern('devicenum')
+    || $self->ut_foreign_key('devicepart', 'part_device', 'devicepart')
+    || $self->ut_foreign_key('svcnum', 'svc_phone', 'svcnum' ) #cust_svc?
+    || $self->ut_hexn('mac_addr')
+  ;
+  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>, schema.html from the base documentation.
+
+=cut
+
+1;
+
index 73ea8e7..badbb4e 100644 (file)
@@ -7,6 +7,7 @@ use FS::Record qw( qsearch qsearchs );
 use FS::Msgcat qw(gettext);
 use FS::svc_Common;
 use FS::part_svc;
+use FS::phone_device;
 
 @ISA = qw( FS::svc_Common );
 
@@ -326,6 +327,17 @@ sub radius_groups {
   ();
 }
 
+=item phone_device
+
+Returns any FS::phone_device records associated with this service.
+
+=cut
+
+sub phone_device {
+  my $self = shift;
+  qsearch('phone_device', { 'svcnum' => $self->svcnum } );
+}
+
 =back
 
 =head1 BUGS
index 7def8df..732e3de 100644 (file)
@@ -445,3 +445,7 @@ FS/cust_main_exemption.pm
 t/cust_main_exemption.t
 FS/cust_tax_adjustment.pm
 t/cust_tax_adjustment.t
+FS/phone_device.pm
+t/phone_device.t
+FS/part_device.pm
+t/part_device.t
diff --git a/FS/t/part_device.t b/FS/t/part_device.t
new file mode 100644 (file)
index 0000000..5696868
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_device;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/phone_device.t b/FS/t/phone_device.t
new file mode 100644 (file)
index 0000000..3070314
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::phone_device;
+$loaded=1;
+print "ok 1\n";
diff --git a/httemplate/browse/part_device.html b/httemplate/browse/part_device.html
new file mode 100644 (file)
index 0000000..5c8fde3
--- /dev/null
@@ -0,0 +1,27 @@
+<% include( 'elements/browse.html',
+                 'title'       => 'Phone device types',
+                 'name'        => 'phone device types',
+                 'menubar'     => [ 'Add a new device type' =>
+                                      $p.'edit/part_device.html',
+                                    'Import device types' =>
+                                      $p.'misc/part_device-import.html',
+                                  ],
+                 'query'       => { 'table' => 'part_device', },
+                 'count_query' => 'SELECT COUNT(*) FROM part_device',
+                 'header'      => [ '#', 'Device type' ],
+                 'fields'      => [ 'devicepart',
+                                    'devicename',
+                                  ],
+                 'links'       => [ $link,
+                                    $link,
+                                  ],
+             )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $link = [ "${p}edit/part_device.html?", 'devicepart' ];
+
+</%init>
diff --git a/httemplate/edit/part_device.html b/httemplate/edit/part_device.html
new file mode 100644 (file)
index 0000000..4f2fe93
--- /dev/null
@@ -0,0 +1,16 @@
+<% include( 'elements/edit.html',
+                 'name'   => 'Phone device type',
+                 'table'  => 'part_device',
+                 'labels' => { 
+                               'devicepart' => 'Part number',
+                               'devicename' => 'Device name',
+                             },
+                 'viewall_dir' => 'browse',
+           )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/phone_device.html b/httemplate/edit/phone_device.html
new file mode 100644 (file)
index 0000000..a1aa166
--- /dev/null
@@ -0,0 +1,37 @@
+<% include( 'elements/edit.html',
+                 'name'   => 'Phone device',
+                 'table'  => 'phone_device',
+                 'labels' => { 
+                               'devicenum'  => 'Device',
+                               'devicepart' => 'Device type',
+                               'mac_addr'   => 'MAC address',
+                             },
+                 'fields' => [ { 'field'    => 'devicepart',
+                                 'type'     => 'select-table',
+                                 'table'    => 'part_device',
+                                 'name_col' => 'devicename',
+                                 'empty_label' =>'Select device type',
+                                 #'hashref'        =>{ disabled => '' },
+                               },
+                               'mac_addr',
+                               { 'field' => 'svcnum',
+                                 'type'  => 'hidden',
+                               },
+                             ],
+                 'menubar' => [], #disable viewall
+                 #'viewall_dir' => 'browse',
+                 'new_callback' => sub {
+                                     my( $cgi, $object ) = @_;
+                                     $object->svcnum( $cgi->param('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?
+
+
+</%init>
diff --git a/httemplate/edit/process/part_device.html b/httemplate/edit/process/part_device.html
new file mode 100644 (file)
index 0000000..2b7e1da
--- /dev/null
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+               'table'       => 'part_device',
+               'viewall_dir' => 'browse',
+           )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/phone_device.html b/httemplate/edit/process/phone_device.html
new file mode 100644 (file)
index 0000000..df9d5e7
--- /dev/null
@@ -0,0 +1,18 @@
+<% include( 'elements/process.html',
+               'table'    => 'phone_device',
+               'redirect' => sub {
+                 my( $cgi, $phone_device ) = @_;
+                 popurl(3).'view/svc_phone.cgi?'.
+                   'svcnum='. $phone_device->svcnum.
+                   ';devicenum=';
+               },
+           )
+%>
+<%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?
+
+</%init>
index cda1efc..b855f79 100644 (file)
@@ -332,6 +332,10 @@ tie my %config_broadband, 'Tie::IxHash',
   'View/Edit address blocks' => [ $fsurl.'browse/addr_block.cgi', 'Manage address blocks and block assignments to broadband routers' ],
 ;
 
+tie my %config_phone, 'Tie::IxHash',
+  'View/Edit phone device types' => [ $fsurl.'browse/part_device.html', 'Phone device types' ],
+;
+
 tie my %config_misc, 'Tie::IxHash';
 $config_misc{'View/Edit advertising sources'} = [ $fsurl.'browse/part_referral.html', 'Where a customer heard about your service.  Tracked for informational purposes' ]
   if $curuser->access_right('Edit advertising sources')
@@ -364,6 +368,8 @@ $config_menu{'Dialup'}  = [ \%config_dialup, ''    ]
   if ( $curuser->access_right('Dialup configuration') );
 $config_menu{'Fixed (username-less) broadband'} = [ \%config_broadband, ''    ]
   if ( $curuser->access_right('Broadband configuration') );
+$config_menu{'Phone'}  = [ \%config_phone, ''    ]
+  if ( $curuser->access_right('Configuration') );
 $config_menu{'Miscellaneous'} = [ \%config_misc, ''    ]
   if $curuser->access_right('Edit advertising sources')
   || $curuser->access_right('Edit global advertising sources');
@@ -393,6 +399,7 @@ $menu{'Configuration'} = [ \%config_menu, 'Configuraiton and setup' ]
   || $curuser->access_right('Edit global billing events')
   || $curuser->access_right('Dialup configuration')
   || $curuser->access_right('Broadband configuration')
+  || $curuser->access_right('Phone configuration')
   || $curuser->access_right('Edit advertising sources')
   || $curuser->access_right('Edit global advertising sources');
 
diff --git a/httemplate/misc/delete-phone_device.html b/httemplate/misc/delete-phone_device.html
new file mode 100755 (executable)
index 0000000..7220c41
--- /dev/null
@@ -0,0 +1,23 @@
+% if ( $error ) {
+%   errorpage($error);
+% } else {
+<% $cgi->redirect($p. "view/svc_phone.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 $phone_device = qsearchs('phone_device', { 'devicenum' => $devicenum } );
+my $svcnum = $phone_device->svcnum;
+
+my $error = $phone_device->delete;
+
+</%init>
diff --git a/httemplate/misc/part_device-import.html b/httemplate/misc/part_device-import.html
new file mode 100644 (file)
index 0000000..7bd6404
--- /dev/null
@@ -0,0 +1,53 @@
+<% include("/elements/header.html", 'Import device types') %>
+
+Import a file containing phone device types, one per line.
+<BR><BR>
+
+<% include( '/elements/form-file_upload.html',
+              'name'      => 'PartDeviceImportForm',
+              'action'    => 'process/part_device-import.html',
+              'num_files' => 1,
+              'fields'    => [ 'format', ], 
+              'message'   => 'Device type import successful',
+              'url'       => $p.'browse/part_device.html',
+          )
+%>
+
+<% &ntable("#cccccc", 2) %>
+
+  <INPUT TYPE="hidden" NAME="format" VALUE="default">
+
+  <% include( '/elements/file-upload.html',
+                'field' => 'file',
+                'label' => 'Filename',
+            )
+  %>
+
+  <TR>
+    <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+      <INPUT TYPE    = "submit"
+             ID      = "submit"
+             VALUE   = "Import file"
+             onClick = "document.PartDeviceImportForm.submit.disabled=true;"
+      >
+    </TD>
+  </TR>
+
+</TABLE>
+
+</FORM>
+
+<BR>
+
+Upload file can be a text file or Excel spreadsheet.  If an Excel spreadsheet,
+ should have an .XLS extension.
+<BR><BR>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+</%init>
diff --git a/httemplate/misc/process/part_device-import.html b/httemplate/misc/process/part_device-import.html
new file mode 100644 (file)
index 0000000..eac111a
--- /dev/null
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+my $server = new FS::UI::Web::JSRPC 'FS::part_device::process_batch_import', $cgi;
+
+</%init>
index f604daa..3d4045b 100644 (file)
 my $html_foot = sub {
   my $svc_phone = shift;
 
+  ###
+  # Devices
+  ###
+
+  my $devices = '';
+
+  my $sth = dbh->prepare("SELECT COUNT(*) FROM part_device") #WHERE disabled = '' OR disabled IS NULL;");
+    or die dbh->errstr;
+  $sth->execute or die $sth->errstr;
+  my $num_part_device = $sth->fetchrow_arrayref->[0];
+
+  my @phone_device = $svc_phone->phone_device;
+  if ( @phone_device || $num_part_device ) {
+    my $svcnum = $svc_phone->svcnum;
+    $devices .=
+      qq[Devices (<A HREF="${p}edit/phone_device.html?svcnum=$svcnum">Add device</A>)<BR>];
+    if ( @phone_device ) {
+
+      $devices .= qq!
+        <SCRIPT>
+          function areyousure(href) {
+           if (confirm("Are you sure you want to delete this device?") == true)
+             window.location.href = href;
+          }
+        </SCRIPT>
+      !;
+
+
+      $devices .= 
+        include('/elements/table-grid.html').
+          '<TR>'.
+            '<TH CLASS="grid" BGCOLOR="#cccccc">Type</TH>'.
+            '<TH CLASS="grid" BGCOLOR="#cccccc">MAC Addr</TH>'.
+            '<TH CLASS="grid" BGCOLOR="#cccccc"></TH>'.
+          '</TR>';
+      my $bgcolor1 = '#eeeeee';
+      my $bgcolor2 = '#ffffff';
+      my $bgcolor = '';
+
+      foreach my $phone_device ( @phone_device ) {
+
+        if ( $bgcolor eq $bgcolor1 ) {
+          $bgcolor = $bgcolor2;
+        } else {
+          $bgcolor = $bgcolor1;
+        }
+        my $td = qq(<TD CLASS="grid" BGCOLOR="$bgcolor">);
+
+        my $devicenum = $phone_device->devicenum;
+
+        $devices .= '<TR>'.
+                      $td. $phone_device->part_device->devicename. '</TD>'.
+                      $td. $phone_device->mac_addr. '</TD>'.
+                      "$td( ".
+                        qq(<A HREF="${p}edit/phone_device.html?$devicenum">edit</A> | ).
+                        qq(<A HREF="javascript:areyousure('${p}misc/delete-phone_device.html?$devicenum')">delete</A>).
+                      ' )</TD>'.
+                    '</TR>';
+      }
+      $devices .= '</TABLE><BR>';
+    }
+    $devices .= '<BR>';
+  }
+
+  ##
+  # CDR links
+  ##
+
   tie my %what, 'Tie::IxHash',
     'pending' => 'NULL',
     'billed'  => 'done',
@@ -46,6 +114,11 @@ my $html_foot = sub {
   my @ilinks = ( qq(<A HREF="${p}search/cdr.html?dst=$number">).
                  'View incoming CDRs</A>' );
 
+  ###
+  # concatenate & return
+  ###
+
+  $devices.
   join(' | ', @links ). '<BR>'.
   join(' | ', @ilinks). '<BR>';