summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FS/FS.pm4
-rw-r--r--FS/FS/Mason.pm2
-rw-r--r--FS/FS/Schema.pm23
-rw-r--r--FS/FS/part_device.pm134
-rw-r--r--FS/FS/phone_device.pm151
-rw-r--r--FS/FS/svc_phone.pm12
-rw-r--r--FS/MANIFEST4
-rw-r--r--FS/t/part_device.t5
-rw-r--r--FS/t/phone_device.t5
-rw-r--r--httemplate/browse/part_device.html27
-rw-r--r--httemplate/edit/part_device.html16
-rw-r--r--httemplate/edit/phone_device.html37
-rw-r--r--httemplate/edit/process/part_device.html11
-rw-r--r--httemplate/edit/process/phone_device.html18
-rw-r--r--httemplate/elements/menu.html7
-rwxr-xr-xhttemplate/misc/delete-phone_device.html23
-rw-r--r--httemplate/misc/part_device-import.html53
-rw-r--r--httemplate/misc/process/part_device-import.html9
-rw-r--r--httemplate/view/svc_phone.cgi73
19 files changed, 614 insertions, 0 deletions
diff --git a/FS/FS.pm b/FS/FS.pm
index c3148735b..e9efcc1cf 100644
--- 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
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index 2a22bfdc5..cce2dbf21 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -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;
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 61cd17e06..4351f2876 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -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
index 000000000..79a534ae7
--- /dev/null
+++ b/FS/FS/part_device.pm
@@ -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
index 000000000..a7097a113
--- /dev/null
+++ b/FS/FS/phone_device.pm
@@ -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;
+
diff --git a/FS/FS/svc_phone.pm b/FS/FS/svc_phone.pm
index 73ea8e731..badbb4eb4 100644
--- a/FS/FS/svc_phone.pm
+++ b/FS/FS/svc_phone.pm
@@ -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
diff --git a/FS/MANIFEST b/FS/MANIFEST
index 7def8dffd..732e3de53 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -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
index 000000000..569686829
--- /dev/null
+++ b/FS/t/part_device.t
@@ -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
index 000000000..307031400
--- /dev/null
+++ b/FS/t/phone_device.t
@@ -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
index 000000000..5c8fde339
--- /dev/null
+++ b/httemplate/browse/part_device.html
@@ -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
index 000000000..4f2fe93b4
--- /dev/null
+++ b/httemplate/edit/part_device.html
@@ -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
index 000000000..a1aa16620
--- /dev/null
+++ b/httemplate/edit/phone_device.html
@@ -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
index 000000000..2b7e1da49
--- /dev/null
+++ b/httemplate/edit/process/part_device.html
@@ -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
index 000000000..df9d5e793
--- /dev/null
+++ b/httemplate/edit/process/phone_device.html
@@ -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>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index cda1efcae..b855f790c 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -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
index 000000000..7220c41e3
--- /dev/null
+++ b/httemplate/misc/delete-phone_device.html
@@ -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
index 000000000..7bd640459
--- /dev/null
+++ b/httemplate/misc/part_device-import.html
@@ -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
index 000000000..eac111a40
--- /dev/null
+++ b/httemplate/misc/process/part_device-import.html
@@ -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>
diff --git a/httemplate/view/svc_phone.cgi b/httemplate/view/svc_phone.cgi
index f604daa47..3d4045b99 100644
--- a/httemplate/view/svc_phone.cgi
+++ b/httemplate/view/svc_phone.cgi
@@ -22,6 +22,74 @@
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>';