summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkhoff <khoff>2002-09-09 23:05:30 +0000
committerkhoff <khoff>2002-09-09 23:05:30 +0000
commit44398c83f25bf4e43838df9f39331c29fdeff19d (patch)
tree963837373a3b621ee2140adad0eb0b44e12c75e6
parent91292eadb6254740a9b72e5dc95f575593f6a35d (diff)
svc_broadband merge
-rw-r--r--FS/FS/ac.pm148
-rwxr-xr-xFS/FS/ac_block.pm148
-rwxr-xr-xFS/FS/ac_field.pm138
-rwxr-xr-xFS/FS/ac_type.pm128
-rw-r--r--FS/FS/cust_svc.pm4
-rwxr-xr-xFS/FS/part_ac_field.pm102
-rw-r--r--FS/FS/part_export.pm3
-rwxr-xr-xFS/FS/svc_broadband.pm295
-rwxr-xr-xbin/fs-setup72
-rw-r--r--htetc/global.asa6
-rwxr-xr-xhttemplate/browse/ac.cgi57
-rwxr-xr-xhttemplate/browse/ac_type.cgi47
-rwxr-xr-xhttemplate/edit/ac.cgi163
-rwxr-xr-xhttemplate/edit/ac_type.cgi106
-rwxr-xr-xhttemplate/edit/part_svc.cgi13
-rwxr-xr-xhttemplate/edit/process/ac.cgi28
-rwxr-xr-xhttemplate/edit/process/ac_block.cgi21
-rwxr-xr-xhttemplate/edit/process/ac_field.cgi21
-rwxr-xr-xhttemplate/edit/process/ac_type.cgi28
-rwxr-xr-xhttemplate/edit/process/part_ac_field.cgi21
-rwxr-xr-xhttemplate/edit/process/part_svc.cgi2
-rw-r--r--httemplate/edit/process/svc_broadband.cgi45
-rw-r--r--httemplate/edit/svc_broadband.cgi219
-rw-r--r--httemplate/index.html4
-rw-r--r--httemplate/view/svc_broadband.cgi75
25 files changed, 1890 insertions, 4 deletions
diff --git a/FS/FS/ac.pm b/FS/FS/ac.pm
new file mode 100644
index 000000000..5a2b36079
--- /dev/null
+++ b/FS/FS/ac.pm
@@ -0,0 +1,148 @@
+package FS::ac;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs qsearch );
+use FS::ac_type;
+use FS::ac_block;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::ac - Object methods for ac records
+
+=head1 SYNOPSIS
+
+ use FS::ac;
+
+ $record = new FS::ac \%hash;
+ $record = new FS::ac { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::ac record describes a broadband Access Concentrator, such as a DSLAM
+or a wireless access point. FS::ac inherits from FS::Record. The following
+fields are currently supported:
+
+narf
+
+=over 4
+
+=item acnum - primary key
+
+=item actypenum - AC type, see L<FS::ac_type>
+
+=item acname - descriptive name for the AC
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Create a new record. To add the record to the database, see L<"insert">.
+
+=cut
+
+sub table { 'ac'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this record from the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item replace OLD_RECORD
+
+Replaces 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('acnum')
+ || $self->ut_number('actypenum')
+ || $self->ut_text('acname');
+ return $error if $error;
+
+ return "Unknown actypenum"
+ unless $self->ac_type;
+ '';
+}
+
+=item ac_type
+
+Returns the L<FS::ac_type> object corresponding to this object.
+
+=cut
+
+sub ac_type {
+ my $self = shift;
+ return qsearchs('ac_type', { actypenum => $self->actypenum });
+}
+
+=item ac_block
+
+Returns a list of L<FS::ac_block> objects (address blocks) associated
+with this object.
+
+=cut
+
+sub ac_block {
+ my $self = shift;
+ return qsearch('ac_block', { acnum => $self->acnum });
+}
+
+=item ac_field
+
+Returns a hash of L<FS::ac_field> objects assigned to this object.
+
+=cut
+
+sub ac_field {
+ my $self = shift;
+
+ return qsearch('ac_field', { acnum => $self->acnum });
+}
+
+=back
+
+=head1 VERSION
+
+$Id:
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::svc_broadband>, L<FS::ac>, L<FS::ac_block>, L<FS::ac_field>, schema.html
+from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/ac_block.pm b/FS/FS/ac_block.pm
new file mode 100755
index 000000000..09de6a4d8
--- /dev/null
+++ b/FS/FS/ac_block.pm
@@ -0,0 +1,148 @@
+package FS::ac_block;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs qsearch );
+use FS::ac_type;
+use FS::ac;
+use FS::svc_broadband;
+use NetAddr::IP;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::ac - Object methods for ac records
+
+=head1 SYNOPSIS
+
+ use FS::ac_block;
+
+ $record = new FS::ac_block \%hash;
+ $record = new FS::ac_block { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::ac_block record describes an address block assigned for broadband
+access. FS::ac_block inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item acnum - the access concentrator (see L<FS::ac_type>) to which this
+block is assigned.
+
+=item ip_gateway - the gateway address used by customers within this block.
+This functions as the primary key.
+
+=item ip_netmask - the netmask of the block, expressed as an integer.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Create a new record. To add the record to the database, see L<"insert">.
+
+=cut
+
+sub table { 'ac_block'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this record from the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item replace OLD_RECORD
+
+Replaces 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_number('acnum')
+ || $self->ut_ip('ip_gateway')
+ || $self->ut_number('ip_netmask')
+ ;
+ return $error if $error;
+
+ return "Unknown acnum"
+ unless $self->ac;
+
+ my $self_addr = new NetAddr::IP ($self->ip_gateway, $self->ip_netmask);
+ return "Cannot parse address: ". $self->ip_gateway . '/' . $self->ip_netmask
+ unless $self_addr;
+
+ my @block = grep {
+ my $block_addr = new NetAddr::IP ($_->ip_gateway, $_->ip_netmask);
+ if($block_addr->contains($self_addr)
+ or $self_addr->contains($block_addr)) { $_; };
+ } qsearch( 'ac_block', {});
+
+ foreach(@block) {
+ return "Block intersects existing block ".$_->ip_gateway."/".$_->ip_netmask;
+ }
+
+ '';
+}
+
+
+=item ac
+
+Returns the L<FS::ac> object corresponding to this object.
+
+=cut
+
+sub ac {
+ my $self = shift;
+ return qsearchs('ac', { acnum => $self->acnum });
+}
+
+=item svc_broadband
+
+Returns a list of L<FS::svc_broadband> objects associated
+with this object.
+
+=cut
+
+#sub svc_broadband {
+# my $self = shift;
+# my @svc = qsearch('svc_broadband', { actypenum => $self->ac->ac_type->actypenum });
+# return grep {
+# my $svc_addr = new NetAddr::IP($_->ip_addr, $_->ip_netmask);
+# $self_addr->contains($svc_addr);
+# } @svc;
+#}
+
+=back
+
+=cut
+
+1;
+
diff --git a/FS/FS/ac_field.pm b/FS/FS/ac_field.pm
new file mode 100755
index 000000000..f6011192f
--- /dev/null
+++ b/FS/FS/ac_field.pm
@@ -0,0 +1,138 @@
+package FS::ac_field;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs );
+use FS::part_ac_field;
+use FS::ac;
+
+use UNIVERSAL qw( can );
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::ac_field - Object methods for ac_field records
+
+=head1 SYNOPSIS
+
+ use FS::ac_field;
+
+ $record = new FS::ac_field \%hash;
+ $record = new FS::ac_field { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+L<FS::ac_field> contains values of fields defined by L<FS::part_ac_field>
+for an L<FS::ac>. Values must be of the data type defined by ut_type in
+L<FS::part_ac_field>.
+Supported fields as follows:
+
+=over 4
+
+=item acfieldpart - Type of ac_field as defined by L<FS::part_ac_field>
+
+=item acnum - The L<FS::ac> to which this value belongs.
+
+=item value - The contents of the field.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Create a new record. To add the record to the database, see L<"insert">.
+
+=cut
+
+sub table { 'ac_field'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this record from the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item replace OLD_RECORD
+
+Replaces 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;
+
+ return "acnum must be defined" unless $self->acnum;
+ return "acfieldpart must be defined" unless $self->acfieldpart;
+
+ my $ut_func = $self->can("ut_" . $self->part_ac_field->ut_type);
+ my $error = $self->$ut_func('value');
+
+ return $error if $error;
+
+ ''; #no error
+}
+
+=item part_ac_field
+
+Returns a reference to the L<FS:part_ac_field> that defines this L<FS::ac_field>
+
+=cut
+
+sub part_ac_field {
+ my $self = shift;
+
+ return qsearchs('part_ac_field', { acfieldpart => $self->acfieldpart });
+}
+
+=item ac
+
+Returns a reference to the L<FS::ac> to which this L<FS::ac_field> belongs.
+
+=cut
+
+sub ac {
+ my $self = shift;
+
+ return qsearchs('ac', { acnum => $self->acnum });
+}
+
+=back
+
+=head1 VERSION
+
+$Id:
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::svc_broadband>, L<FS::ac>, L<FS::ac_block>, L<FS::ac_field>, schema.html
+from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/ac_type.pm b/FS/FS/ac_type.pm
new file mode 100755
index 000000000..e83c5c5f0
--- /dev/null
+++ b/FS/FS/ac_type.pm
@@ -0,0 +1,128 @@
+package FS::ac_type;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs );
+use FS::ac;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::ac_type - Object methods for ac_type records
+
+=head1 SYNOPSIS
+
+ use FS::ac_type;
+
+ $record = new FS::ac_type \%hash;
+ $record = new FS::ac_type { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+L<FS::ac_type> refers to a type of access concentrator. L<FS::svc_broadband>
+records refer to a specific L<FS::ac_type> limiting the choice of access
+concentrator to one of the chosen type. This should be set as a fixed
+default in part_svc to prevent provisioning the wrong type of service for
+a given package or service type. Supported fields as follows:
+
+=over 4
+
+=item actypenum - Primary key. see L<FS::ac>
+
+=item actypename - Text identifier for access concentrator type.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Create a new record. To add the record to the database, see L<"insert">.
+
+=cut
+
+sub table { 'ac_type'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this record from the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item replace OLD_RECORD
+
+Replaces 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;
+
+ # What do we check?
+
+ ''; #no error
+}
+
+=item ac
+
+Returns a list of all L<FS::ac> records of this type.
+
+=cut
+
+sub ac {
+ my $self = shift;
+
+ return qsearch('ac', { actypenum => $self->actypenum });
+}
+
+=item part_ac_field
+
+Returns a list of all L<FS::part_ac_field> records of this type.
+
+=cut
+
+sub part_ac_field {
+ my $self = shift;
+
+ return qsearch('part_ac_field', { actypenum => $self->actypenum });
+}
+
+=back
+
+=head1 VERSION
+
+$Id:
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::svc_broadband>, L<FS::ac>, L<FS::ac_block>, L<FS::ac_field>, schema.html
+from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm
index c7cc4b322..d54fb2d40 100644
--- a/FS/FS/cust_svc.pm
+++ b/FS/FS/cust_svc.pm
@@ -295,6 +295,8 @@ sub label {
} elsif ( $svcdb eq 'svc_www' ) {
my $domain = qsearchs( 'domain_record', { 'recnum' => $svc_x->recnum } );
$tag = $domain->reczone;
+ } elsif ( $svcdb eq 'svc_broadband' ) {
+ $tag = $svc_x->ip_addr . '/' . $svc_x->ip_netmask;
} else {
cluck "warning: asked for label of unsupported svcdb; using svcnum";
$tag = $svc_x->getfield('svcnum');
@@ -344,7 +346,7 @@ sub seconds_since {
=head1 VERSION
-$Id: cust_svc.pm,v 1.15 2002-05-22 12:17:06 ivan Exp $
+$Id: cust_svc.pm,v 1.16 2002-09-09 23:01:35 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/part_ac_field.pm b/FS/FS/part_ac_field.pm
new file mode 100755
index 000000000..dcb445253
--- /dev/null
+++ b/FS/FS/part_ac_field.pm
@@ -0,0 +1,102 @@
+package FS::part_ac_field;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs );
+use FS::ac_field;
+use FS::ac;
+
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::part_ac_field - Object methods for part_ac_field records
+
+=head1 SYNOPSIS
+
+ use FS::part_ac_field;
+
+ $record = new FS::part_ac_field \%hash;
+ $record = new FS::part_ac_field { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+
+=over 4
+
+=item blank
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Create a new record. To add the record to the database, see L<"insert">.
+
+=cut
+
+sub table { 'part_ac_field'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this record from the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item replace OLD_RECORD
+
+Replaces 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->name =~ /^([a-z0-9_\-\.]{1,15})$/i
+ or return "Invalid field name for part_ac_field";
+
+ ''; #no error
+}
+
+
+=back
+
+=head1 VERSION
+
+$Id:
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::svc_broadband>, L<FS::ac>, L<FS::ac_block>, L<FS::ac_field>, schema.html
+from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_export.pm b/FS/FS/part_export.pm
index bc6a4d735..69cd8058b 100644
--- a/FS/FS/part_export.pm
+++ b/FS/FS/part_export.pm
@@ -839,6 +839,9 @@ tie my %sqlmail_options, 'Tie::IxHash',
},
+ 'svc_broadband' => {
+ },
+
);
=back
diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm
new file mode 100755
index 000000000..ab92fb3d7
--- /dev/null
+++ b/FS/FS/svc_broadband.pm
@@ -0,0 +1,295 @@
+package FS::svc_broadband;
+
+use strict;
+use vars qw(@ISA $conf);
+#use FS::Record qw( qsearch qsearchs );
+use FS::Record qw( qsearchs qsearch dbh );
+use FS::svc_Common;
+use FS::cust_svc;
+use NetAddr::IP;
+
+@ISA = qw( FS::svc_Common );
+
+$FS::UID::callback{'FS::svc_broadband'} = sub {
+ $conf = new FS::Conf;
+};
+
+=head1 NAME
+
+FS::svc_broadband - Object methods for svc_broadband records
+
+=head1 SYNOPSIS
+
+ use FS::svc_broadband;
+
+ $record = new FS::svc_broadband \%hash;
+ $record = new FS::svc_broadband { '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;
+
+=head1 DESCRIPTION
+
+An FS::svc_broadband object represents a 'broadband' Internet connection, such
+as a DSL, cable modem, or fixed wireless link. These services are assumed to
+have the following properties:
+
+=over 2
+
+=item
+The network consists of one or more 'Access Concentrators' (ACs), such as
+DSLAMs or wireless access points. (See L<FS::ac>.)
+
+=item
+Each AC provides connectivity to one or more contiguous blocks of IP addresses,
+each described by a gateway address and a netmask. (See L<FS::ac_block>.)
+
+=item
+Each connection has one or more static IP addresses within one of these blocks.
+
+=item
+The details of configuring routers and other devices are to be handled by a
+site-specific L<FS::part_export> subclass.
+
+=back
+
+FS::svc_broadband inherits from FS::svc_Common. The following fields are
+currently supported:
+
+=over 4
+
+=item svcnum - primary key
+
+=item
+actypenum - access concentrator type; see L<FS::ac_type>. This is included here
+so that a part_svc can specifically be a 'wireless' or 'DSL' service by
+designating actypenum as a fixed field. It does create a redundant functional
+dependency between this table and ac_type, in that the matching ac_type could
+be found by looking up the IP address in ac_block and then finding the block's
+AC, but part_svc can't do that, and we don't feel like hacking it so that it
+can.
+
+=item
+speed_up - maximum upload speed, in bits per second. If set to zero, upload
+speed will be unlimited. Exports that do traffic shaping should handle this
+correctly, and not blindly set the upload speed to zero and kill the customer's
+connection.
+
+=item
+speed_down - maximum download speed, as above
+
+=item
+ip_addr - the customer's IP address. If the customer needs more than one IP
+address, set this to the address of the customer's router. As a result, the
+customer's router will have the same address for both it's internal and external
+interfaces thus saving address space. This has been found to work on most NAT
+routers available.
+
+=item
+ip_netmask - the customer's netmask, as a single integer in the range 0-32.
+(E.g. '24', not '255.255.255.0'. We assume that address blocks are contiguous.)
+This should be 32 unless the customer has multiple IP addresses.
+
+=item
+mac_addr - the MAC address of the customer's router or other device directly
+connected to the network, if needed. Some systems (e.g. DHCP, MAC address-based
+access control) may need this. If not, you may leave it blank.
+
+=item
+location - a human-readable description of the location of the connected site,
+such as its address. This should not be used for billing or contact purposes;
+that information is stored in L<FS::cust_main>.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new svc_broadband. 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_broadband'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
+defined. An FS::cust_svc record will be created and inserted.
+
+=cut
+
+# sub insert {}
+# Standard FS::svc_Common::insert
+# (any necessary Deep Magic is handled by exports)
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# Standard FS::svc_Common::delete
+
+=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
+
+# Standard FS::svc_Common::replace
+# Notice a pattern here?
+
+=item suspend
+
+Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item unsuspend
+
+Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item cancel
+
+Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item check
+
+Checks all fields to make sure this is a valid broadband service. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+ my $x = $self->setfixed;
+
+ return $x unless ref($x);
+
+ my $error =
+ $self->ut_numbern('svcnum')
+ || $self->ut_foreign_key('actypenum', 'ac_type', 'actypenum')
+ || $self->ut_number('speed_up')
+ || $self->ut_number('speed_down')
+ || $self->ut_ip('ip_addr')
+ || $self->ut_numbern('ip_netmask')
+ || $self->ut_textn('mac_addr')
+ || $self->ut_textn('location')
+ ;
+ return $error if $error;
+
+ if($self->speed_up < 0) { return 'speed_up must be positive'; }
+ if($self->speed_down < 0) { return 'speed_down must be positive'; }
+
+ # This should catch errors in the ip_addr and ip_netmask. If it doesn't,
+ # they'll almost certainly not map into a valid block anyway.
+ my $self_addr = new NetAddr::IP ($self->ip_addr, $self->ip_netmask);
+ return 'Cannot parse address: ' . $self->ip_addr . '/' . $self->ip_netmask unless $self_addr;
+
+ my @block = grep {
+ my $block_addr = new NetAddr::IP ($_->ip_gateway, $_->ip_netmask);
+ if ($block_addr->contains($self_addr)) { $_ };
+ } qsearch( 'ac_block', { acnum => $self->acnum });
+
+ if(scalar @block == 0) {
+ return 'Block not found for address '.$self->ip_addr.' in actype '.$self->actypenum;
+ } elsif(scalar @block > 1) {
+ return 'ERROR: Intersecting blocks found for address '.$self->ip_addr.' :'.
+ join ', ', map {$_->ip_addr . '/' . $_->ip_netmask} @block;
+ }
+ # OK, we've found a valid block. We don't actually _do_ anything with it, though; we
+ # just take comfort in the knowledge that it exists.
+
+ # A simple qsearchs won't work here. Since we can assign blocks to customers,
+ # we have to make sure the new address doesn't fall within someone else's
+ # block. Ugh.
+
+ my @conflicts = grep {
+ my $cust_addr = new NetAddr::IP($_->ip_addr, $_->ip_netmask);
+ if (($cust_addr->contains($self_addr)) and
+ ($_->svcnum ne $self->svcnum)) { $_; };
+ } qsearch('svc_broadband', {});
+
+ if (scalar @conflicts > 0) {
+ return 'Address in use by existing service';
+ }
+
+ # Are we trying to use a network, broadcast, or the AC's address?
+ foreach (qsearch('ac_block', { acnum => $self->acnum })) {
+ my $block_addr = new NetAddr::IP($_->ip_gateway, $_->ip_netmask);
+ if ($block_addr->network->addr eq $self_addr->addr) {
+ return 'Address is network address for block '. $block_addr->network;
+ }
+ if ($block_addr->broadcast->addr eq $self_addr->addr) {
+ return 'Address is broadcast address for block '. $block_addr->network;
+ }
+ if ($block_addr->addr eq $self_addr->addr) {
+ return 'Address belongs to the access concentrator: '. $block_addr->addr;
+ }
+ }
+
+ ''; #no error
+}
+
+=item ac_block
+
+Returns the FS::ac_block record (i.e. the address block) for this broadband service.
+
+=cut
+
+sub ac_block {
+ my $self = shift;
+ my $self_addr = new NetAddr::IP ($self->ip_addr, $self->ip_netmask);
+
+ foreach my $block (qsearch( 'ac_block', {} )) {
+ my $block_addr = new NetAddr::IP ($block->ip_addr, $block->ip_netmask);
+ if($block_addr->contains($self_addr)) { return $block; }
+ }
+ return '';
+}
+
+=item ac_type
+
+Returns the FS::ac_type record for this broadband service.
+
+=cut
+
+sub ac_type {
+ my $self = shift;
+ return qsearchs('ac_type', { actypenum => $self->actypenum });
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::svc_Common>, L<FS::Record>, L<FS::ac_type>, L<FS::ac_block>,
+L<FS::part_svc>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/bin/fs-setup b/bin/fs-setup
index 9522ce370..81f1c26b0 100755
--- a/bin/fs-setup
+++ b/bin/fs-setup
@@ -1,6 +1,6 @@
#!/usr/bin/perl -Tw
#
-# $Id: fs-setup,v 1.96 2002-07-06 12:13:49 ivan Exp $
+# $Id: fs-setup,v 1.97 2002-09-09 23:01:36 khoff Exp $
#to delay loading dbdef until we're ready
BEGIN { $FS::Record::setup_hack = 1; }
@@ -1023,7 +1023,77 @@ sub tables_hash_hack {
'index' => [],
},
+ 'ac_type' => {
+ 'columns' => [
+ 'actypenum', 'int', '', '',
+ 'actypename', 'varchar', '', 15,
+ ],
+ 'primary_key' => 'actypenum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'ac' => {
+ 'columns' => [
+ 'acnum', 'int', '', '',
+ 'actypenum', 'int', '', '',
+ 'acname', 'varchar', '', 15,
+ ],
+ 'primary_key' => 'acnum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'part_ac_field' => {
+ 'columns' => [
+ 'acfieldpart', 'int', '', '',
+ 'actypenum', 'int', '', '',
+ 'name', 'varchar', '', 15,
+ 'ut_type', 'varchar', '', 15,
+ ],
+ 'primary_key' => 'acfieldpart',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'ac_field' => {
+ 'columns' => [
+ 'acfieldpart', 'int', '', '',
+ 'acnum', 'int', '', '',
+ 'value', 'varchar', '', 127,
+ ],
+ 'primary_key' => '',
+ 'unique' => [ [ 'acfieldpart', 'acnum' ] ],
+ 'index' => [],
+ },
+
+ 'ac_block' => {
+ 'columns' => [
+ 'acnum', 'int', '', '',
+ 'ip_gateway', 'char', '', 15,
+ 'ip_netmask', 'int', '', '',
+ ],
+ 'primary_key' => '',
+ 'unique' => [],
+ 'index' => [],
+ },
+ 'svc_broadband' => {
+ 'columns' => [
+ 'svcnum', 'int', '', '',
+ 'actypenum', 'int', '', '',
+ 'speed_up', 'int', '', '',
+ 'speed_down', 'int', '', '',
+ 'acnum', 'int', '', '',
+ 'ip_addr', 'char', '', 15,
+ 'ip_netmask', 'int', '', '',
+ 'mac_addr', 'char', '', 17,
+ 'location', 'varchar', '', 127,
+ ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [],
+ },
);
diff --git a/htetc/global.asa b/htetc/global.asa
index d04a5edbf..3c8380fd4 100644
--- a/htetc/global.asa
+++ b/htetc/global.asa
@@ -46,6 +46,12 @@ use FS::svc_acct_sm;
use FS::svc_domain;
use FS::svc_forward;
use FS::svc_www;
+use FS::ac_type;
+use FS::ac;
+use FS::part_ac_field;
+use FS::ac_field;
+use FS::ac_block;
+use FS::svc_broadband;
use FS::type_pkgs;
use FS::part_export;
use FS::part_export_option;
diff --git a/httemplate/browse/ac.cgi b/httemplate/browse/ac.cgi
new file mode 100755
index 000000000..0ae138d3b
--- /dev/null
+++ b/httemplate/browse/ac.cgi
@@ -0,0 +1,57 @@
+<!-- mason kludge -->
+<%= header('Access Concentrator Listing', menubar(
+ 'Main Menu' => $p,
+ 'Access Concentrator Types' => $p. 'browse/ac_type.cgi',
+)) %>
+<BR>
+<A HREF="<%= $p %>edit/ac.cgi"><I>Add a new Access Concentrator</I></A><BR><BR>
+
+<%= table() %>
+<TR>
+ <TH COLSPAN=2>AC</TH>
+ <TH>AC Type</TH>
+ <TH>Fields</TH>
+ <TH>Network/Mask</TH>
+</TR>
+<%
+
+foreach my $ac ( qsearch('ac',{}) ) {
+ my($hashref)=$ac->hashref;
+ my($actypenum)=$hashref->{actypenum};
+ my($ac_type)=qsearchs('ac_type',{'actypenum'=>$actypenum});
+ my($actypename)=$ac_type->getfield('actypename');
+ print <<END;
+ <TR>
+ <TD><A HREF="${p}edit/ac.cgi?$hashref->{acnum}">
+ $hashref->{acnum}</A></TD>
+ <TD><A HREF="${p}edit/ac.cgi?$hashref->{acnum}">
+ $hashref->{acname}</A></TD>
+ <TD><A HREF="${p}edit/ac_type.cgi?$actypenum">$actypename</A></TD>
+ <TD>
+END
+
+ foreach my $ac_field ( qsearch('ac_field', { acnum => $hashref->{acnum} }) ) {
+ my $part_ac_field = qsearchs('part_ac_field',
+ { acfieldpart => $ac_field->getfield('acfieldpart') });
+ print $part_ac_field->getfield('name') . ' ';
+ print $ac_field->getfield('value') . '<BR>';
+ }
+ print '</TD><TD>';
+
+ foreach (qsearch('ac_block', { acnum => $hashref->{acnum} })) {
+ my $net_addr = new NetAddr::IP($_->getfield('ip_gateway'),
+ $_->getfield('ip_netmask'));
+ print $net_addr->network->addr . '/' . $net_addr->network->mask . '<BR>';
+ }
+
+ print "<TR>\n";
+
+}
+
+print <<END;
+ </TABLE>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/browse/ac_type.cgi b/httemplate/browse/ac_type.cgi
new file mode 100755
index 000000000..0ad8271d3
--- /dev/null
+++ b/httemplate/browse/ac_type.cgi
@@ -0,0 +1,47 @@
+<!-- mason kludge -->
+<%
+
+print header('Access Concentrator Types', menubar(
+ 'Main Menu' => $p,
+ 'Access Concentrators' => $p. 'browse/ac.cgi',
+)) %>
+<BR>
+<A HREF="<%= $p %>edit/ac_type.cgi"><I>Add new AC Type</I></A><BR><BR>
+<%= table() %>
+ <TR>
+ <TH></TH>
+ <TH>Type</TH>
+ <TH>Fields</TH>
+ </TR>
+
+<%
+foreach my $ac_type ( qsearch('ac_type',{}) ) {
+ my($hashref)=$ac_type->hashref;
+ print <<END;
+ <TR>
+ <TD><A HREF="${p}edit/ac_type.cgi?$hashref->{actypenum}">
+ $hashref->{actypenum}</A></TD>
+ <TD><A HREF="${p}edit/ac_type.cgi?$hashref->{actypenum}">
+ $hashref->{actypename}</A></TD>
+ <TD>
+END
+
+ foreach ( qsearch('part_ac_field', { actypenum => $hashref->{actypenum} }) ) {
+ my ($part_ac_field) = $_->hashref;
+ print $part_ac_field->{'name'} .
+ ' (' . $part_ac_field->{'ut_type'} . ')<BR>';
+ }
+
+}
+
+print <<END;
+ </TD>
+ </TR>
+ <TR>
+ </TR>
+ </TABLE>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/edit/ac.cgi b/httemplate/edit/ac.cgi
new file mode 100755
index 000000000..86b05a4a1
--- /dev/null
+++ b/httemplate/edit/ac.cgi
@@ -0,0 +1,163 @@
+<!-- mason kludge -->
+<%
+
+my($ac);
+if ( $cgi->param('error') ) {
+ $ac = new FS::ac ( {
+ map { $_, scalar($cgi->param($_)) } fields('ac')
+ } );
+} elsif ( $cgi->keywords ) { #editing
+ my( $query ) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $ac=qsearchs('ac',{'acnum'=>$1});
+} else { #adding
+ $ac = new FS::ac {};
+}
+my $action = $ac->acnum ? 'Edit' : 'Add';
+my $hashref = $ac->hashref;
+
+print header("$action Access Concentrator", menubar(
+ 'Main Menu' => "$p",
+ 'View all access concentrators' => "${p}browse/ac.cgi",
+));
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+print '<FORM ACTION="', popurl(1), 'process/ac.cgi" METHOD=POST>',
+ qq!<INPUT TYPE="hidden" NAME="acnum" VALUE="$hashref->{acnum}">!,
+ "Access Concentrator #", $hashref->{acnum} ? $hashref->{acnum} : "(NEW)";
+
+print <<END;
+
+<TABLE COLOR="#cccccc">
+ <TR>
+ <TH ALIGN="RIGHT">Access Concentrator</TH>
+ <TD>
+ <INPUT TYPE="text" NAME="acname" SIZE=15 VALUE="$hashref->{acname}">
+ </TD>
+ </TD>
+END
+
+
+if (! $ac->acnum) {
+ print <<END;
+ <TR>
+ <TH ALIGN="RIGHT">Access Concentrator Type</TH>
+ <TD><SELECT NAME="actypenum" SIZE="1"><OPTION VALUE=""></OPTION>
+END
+
+ foreach (qsearch('ac_type', {})) {
+ my $narf = $_->hashref;
+ print qq!<OPTION! .
+ ($narf->{actypenum} eq $hashref->{actypenum} ? ' SELECTED' : '') .
+ qq! VALUE="$narf->{actypenum}">$narf->{actypenum}: $narf->{actypename}! .
+ qq!</OPTION>!;
+ }
+
+ print '</TD></TR></TABLE>';
+} else {
+ print '</TABLE>';
+ print qq!<INPUT TYPE="hidden" NAME="actypenum" VALUE="$hashref->{actypenum}">!;
+}
+
+print qq!</TABLE><BR><BR><INPUT TYPE="submit" VALUE="!,
+ $hashref->{acnum} ? "Apply changes" : "Add access concentrator",
+ qq!"></FORM>!;
+
+if ($hashref->{acnum}) {
+ print table();
+ print <<END;
+ Additional Fields:<BR>
+ <TH>
+ <TD>Field Name</TD>
+ <TD COLSPAN=2>Field Value</TD>
+ </TH>
+END
+
+ #my @ac_fields = qsearch('ac_field', { acnum => $hashref->{acnum} });
+ my @ac_fields = $ac->ac_field;
+ foreach (@ac_fields) {
+ print qq!\n<TR><TD></TD>!;
+ my $part_ac_field = qsearchs('part_ac_field',
+ { acfieldpart => $_->getfield('acfieldpart') });
+ print '<TD>' . $part_ac_field->getfield('name') .
+ '</TD><TD>' . $_->getfield('value') . '</TD></TR>';
+ print "\n";
+ }
+
+ print '<FORM ACTION="', popurl(1), 'process/ac_field.cgi" METHOD=POST>';
+ print <<END;
+ <TR>
+ <TD><INPUT TYPE="hidden" NAME="acnum" VALUE="$hashref->{acnum}">
+ <INPUT TYPE="hidden" NAME="acname" VALUE="$hashref->{acname}">
+ <INPuT TYPE="hidden" NAME="actypenum" VALUE="$hashref->{actypenum}">
+ <SMALL>(NEW)</SMALL>
+ </TD>
+ <TD><SELECT NAME="acfieldpart"><OPTION></OPTION>
+END
+
+ my @part_ac_fields = qsearch('part_ac_field',
+ { actypenum => $hashref->{actypenum} });
+ foreach my $part_ac_field (@part_ac_fields) {
+ my $acfieldpart = $part_ac_field->getfield('acfieldpart');
+ if (grep {$_->getfield('acfieldpart') eq $acfieldpart} @ac_fields) {next;}
+ print qq!<OPTION VALUE="${acfieldpart}">! .
+ $part_ac_field->getfield('name') . '</OPTION>';
+ }
+
+ print <<END;
+ </SELECT>
+ </TD>
+ <TD><INPUT TYPE="text" SIZE="15" NAME="value"></TD>
+ <TD><INPUT TYPE="submit" VALUE="Add"></TD>
+ </TR>
+ </FORM>
+ </TABLE>
+END
+
+}
+
+if ($hashref->{acnum}) {
+
+ print qq!<BR><BR>IP Address Blocks:<BR>! . table() .
+ qq!<TR><TH></TH><TH>Network/Mask</TH>! .
+ qq!<TH>Gateway Address</TH><TH>Mask length</TH></TR>\n!;
+
+ foreach (qsearch('ac_block', { acnum => $hashref->{acnum} })) {
+ my $ip_addr = new NetAddr::IP($_->getfield('ip_gateway'),
+ $_->getfield('ip_netmask'));
+ print qq!<TR><TD></TD><TD>! . $ip_addr->network->addr() . '/' .
+ $ip_addr->network->mask() . qq!</TD>!;
+
+ print qq!<TD>! . $_->getfield('ip_gateway') . qq!</TD>\n! .
+ qq!<TD>! . $_->getfield('ip_netmask') . qq!</TD></TR>!;
+
+ }
+
+ print '<FORM ACTION="', popurl(1), 'process/ac_block.cgi" METHOD=POST>';
+ print <<END;
+ <TR>
+ <TD><INPUT TYPE="hidden" NAME="acnum" VALUE="$hashref->{acnum}">
+ <INPUT TYPE="hidden" NAME="acname" VALUE="$hashref->{acname}">
+ <INPuT TYPE="hidden" NAME="actypenum" VALUE="$hashref->{actypenum}">
+ <SMALL>(NEW)</SMALL>
+ </TD>
+ <TD></TD>
+ <TD><INPUT TYPE="text" NAME="ip_gateway" SIZE="15"></TD>
+ <TD><INPUT TYPE="text" NAME="ip_netmask" SIZE="2"></TD>
+ <TD><INPUT TYPE="submit" VALUE="Add"></TD>
+ </TR>
+ </FORM>
+</TABLE>
+END
+
+}
+
+print <<END;
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/edit/ac_type.cgi b/httemplate/edit/ac_type.cgi
new file mode 100755
index 000000000..ccc3d579c
--- /dev/null
+++ b/httemplate/edit/ac_type.cgi
@@ -0,0 +1,106 @@
+<!-- mason kludge -->
+<%
+
+my $ac_type;
+if ( $cgi->param('error') ) {
+ $ac_type = new FS::ac_type ( {
+ map { $_, scalar($cgi->param($_)) } fields('ac_type')
+ } );
+} elsif ( $cgi->keywords ) { #editing
+ my($query)=$cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $ac_type=qsearchs('ac_type',{'actypenum'=>$1});
+} else { #adding
+ $ac_type = new FS::ac_type {};
+}
+my $action = $ac_type->actypenum ? 'Edit' : 'Add';
+my $hashref = $ac_type->hashref;
+
+my @ut_types = qw( float number text alpha anything ip domain );
+
+my $p1 = popurl(1);
+print header("$action Access Concentrator Type", menubar(
+ 'Main Menu' => popurl(2),
+ 'View all Access Concentrator types' => popurl(2). "browse/ac_type.cgi",
+));
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+print qq!<FORM ACTION="${p1}process/ac_type.cgi" METHOD=POST>!;
+
+#display
+
+print qq!<INPUT TYPE="hidden" NAME="actypenum" VALUE="$hashref->{actypenum}">!,
+ "AC Type #", $hashref->{actypenum} ? $hashref->{actypenum} : "(NEW)";
+
+print <<TROZ;
+<PRE>
+AC Type Name <INPUT TYPE="text" NAME="actypename" SIZE=15 VALUE="$hashref->{actypename}">
+</PRE>
+
+TROZ
+
+print qq!<BR><INPUT TYPE="submit" VALUE="!,
+ $hashref->{actypenum} ? "Apply changes" : "Add AC Type",
+ qq!"></FORM>!;
+
+
+if ($hashref->{actypenum}) {
+ print qq! <BR>Available fields:<BR>! . table();
+
+ print qq! <TH><TD>Field name</TD><TD>Field type</TD><TD></TD></TH>!;
+
+ my @part_ac_field = qsearch ( 'part_ac_field',
+ { actypenum => $hashref->{actypenum} } );
+ foreach ( @part_ac_field ) {
+ my $pf_hashref = $_->hashref;
+ print <<END;
+ <TR>
+ <TD>$pf_hashref->{acfieldpart}</TD>
+ <TD>$pf_hashref->{name}</TD>
+ <TD>$pf_hashref->{ut_type}</TD>
+ </TR>
+END
+ }
+
+ my $name, $ut_type = '';
+ if ($cgi->param('error')) {
+ $name = $cgi->param('name');
+ $ut_type = $cgi->param('ut_type');
+ }
+
+ print <<END;
+ <FORM ACTION="${p1}process/part_ac_field.cgi" METHOD=GET>
+ <TR>
+ <TD><SMALL>(NEW)</SMALL>
+ <INPUT TYPE="hidden" NAME="actypenum" VALUE="$hashref->{actypenum}">
+ </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="name" VALUE="${name}">
+ </TD>
+ <TD>
+ <SELECT NAME="ut_type" SIZE=1><OPTION>
+END
+
+ foreach ( @ut_types ) {
+ print qq!<OPTION! . ($ut_type ? " SELECTED>$_" : ">$_");
+ }
+
+ print <<END;
+ </SELECT>
+ </TD>
+ <TD><INPUT TYPE="submit" VALUE="Add"></TD>
+ </TR>
+ </FORM>
+ </TABLE>
+END
+
+}
+
+%>
+
+ </BODY>
+</HTML>
+
diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi
index 4ccb770fb..a23107a40 100755
--- a/httemplate/edit/part_svc.cgi
+++ b/httemplate/edit/part_svc.cgi
@@ -53,6 +53,7 @@ Services are items you offer to your customers.
<LI>svc_acct_sm - <B>deprecated</B> (use svc_forward for new installations) Virtual domain mail aliasing.
<LI>svc_forward - mail forwarding
<LI>svc_www - Virtual domain website
+ <LI>svc_broadband - Broadband/High-speed Internet service
<!-- <LI>svc_charge - One-time charges (Partially unimplemented)
<LI>svc_wo - Work orders (Partially unimplemented)
-->
@@ -122,11 +123,21 @@ my %defs = (
#'recnum' => '',
#'usersvc' => '',
},
+ 'svc_broadband' => {
+ 'actypenum' => 'This is the actypenum that refers to the type of AC that can be provisioned for this service. This field must be set fixed.',
+ 'speed_down' => 'Maximum download speed for this service in Kbps. 0 denotes unlimited.',
+ 'speed_up' => 'Maximum upload speed for this service in Kbps. 0 denotes unlimited.',
+ 'acnum' => 'acnum of a specific AC that this service is restricted to. Not required',
+ 'ip_addr' => 'IP address. Leave blank for automatic assignment.',
+ 'ip_netmask' => 'Mask length, aka. netmask bits. (Eg. 255.255.255.0 == 24)',
+ 'mac_addr' => 'MAC address which is used by some ACs for access control. Specified by 6 colon seperated hex octets. (Eg. 00:00:0a:bc:1a:2b)',
+ 'location' => 'Defines the physically location at which this service was installed. This is not necessarily the billing address',
+ },
);
my @dbs = $hashref->{svcdb}
? ( $hashref->{svcdb} )
- : qw( svc_acct svc_domain svc_acct_sm svc_forward svc_www );
+ : qw( svc_acct svc_domain svc_acct_sm svc_forward svc_www svc_broadband );
tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } @dbs;
my $widget = new HTML::Widgets::SelectLayers(
diff --git a/httemplate/edit/process/ac.cgi b/httemplate/edit/process/ac.cgi
new file mode 100755
index 000000000..fc434a807
--- /dev/null
+++ b/httemplate/edit/process/ac.cgi
@@ -0,0 +1,28 @@
+<%
+
+my $acnum = $cgi->param('acnum');
+
+my $old = qsearchs('ac',{'acnum'=>$acnum}) if $acnum;
+
+my $new = new FS::ac ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('ac')
+} );
+
+my $error = '';
+if ( $acnum ) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $acnum=$new->getfield('acnum');
+}
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "ac.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "browse/ac.cgi");
+}
+
+%>
diff --git a/httemplate/edit/process/ac_block.cgi b/httemplate/edit/process/ac_block.cgi
new file mode 100755
index 000000000..b1c3c726b
--- /dev/null
+++ b/httemplate/edit/process/ac_block.cgi
@@ -0,0 +1,21 @@
+<%
+
+my $new = new FS::ac_block ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('ac_block')
+} );
+
+my $error = '';
+$error = $new->check;
+
+unless ( $error ) { $error = $new->insert; }
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "ac.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(2). "ac.cgi?". $cgi->param('acnum'));
+}
+
+%>
diff --git a/httemplate/edit/process/ac_field.cgi b/httemplate/edit/process/ac_field.cgi
new file mode 100755
index 000000000..2bfe3312f
--- /dev/null
+++ b/httemplate/edit/process/ac_field.cgi
@@ -0,0 +1,21 @@
+<%
+
+my $new = new FS::ac_field ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('ac_field')
+} );
+
+my $error = '';
+$error = $new->check;
+
+unless ( $error ) { $error = $new->insert; }
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "ac.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(2). "ac.cgi?". $cgi->param('acnum'));
+}
+
+%>
diff --git a/httemplate/edit/process/ac_type.cgi b/httemplate/edit/process/ac_type.cgi
new file mode 100755
index 000000000..ca232ba58
--- /dev/null
+++ b/httemplate/edit/process/ac_type.cgi
@@ -0,0 +1,28 @@
+<%
+
+my $actypenum = $cgi->param('actypenum');
+
+my $old = qsearchs('ac_type',{'actypenum'=>$actypenum}) if $actypenum;
+
+my $new = new FS::ac_type ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('ac_type')
+} );
+
+my $error = '';
+if ( $actypenum ) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $actypenum=$new->getfield('actypenum');
+}
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "ac_type.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "browse/ac_type.cgi");
+}
+
+%>
diff --git a/httemplate/edit/process/part_ac_field.cgi b/httemplate/edit/process/part_ac_field.cgi
new file mode 100755
index 000000000..38ad586f7
--- /dev/null
+++ b/httemplate/edit/process/part_ac_field.cgi
@@ -0,0 +1,21 @@
+<%
+
+my $new = new FS::part_ac_field ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('part_ac_field')
+} );
+
+my $error = '';
+$error = $new->check;
+
+unless ( $error ) { $error = $new->insert; }
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "ac_type.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(2). "ac_type.cgi?". $cgi->param('actypenum'));
+}
+
+%>
diff --git a/httemplate/edit/process/part_svc.cgi b/httemplate/edit/process/part_svc.cgi
index 859670b17..69e8ac2fa 100755
--- a/httemplate/edit/process/part_svc.cgi
+++ b/httemplate/edit/process/part_svc.cgi
@@ -17,7 +17,7 @@ my $new = new FS::part_svc ( {
push @fields, 'usergroup' if $svcdb eq 'svc_acct'; #kludge
map { ( $svcdb.'__'.$_, $svcdb.'__'.$_.'_flag' ) } @fields;
} grep defined( $FS::Record::dbdef->table($_) ),
- qw( svc_acct svc_domain svc_acct_sm svc_forward svc_www )
+ qw( svc_acct svc_domain svc_acct_sm svc_forward svc_www svc_broadband )
)
} );
diff --git a/httemplate/edit/process/svc_broadband.cgi b/httemplate/edit/process/svc_broadband.cgi
new file mode 100644
index 000000000..fd7ba20d5
--- /dev/null
+++ b/httemplate/edit/process/svc_broadband.cgi
@@ -0,0 +1,45 @@
+<%
+
+$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+my $svcnum = $1;
+
+my $old;
+if ( $svcnum ) {
+ $old = qsearchs('svc_broadband', { 'svcnum' => $svcnum } )
+ or die "fatal: can't find broadband service (svcnum $svcnum)!";
+} else {
+ $old = '';
+}
+
+my $new = new FS::svc_broadband ( {
+ map {
+ ($_, scalar($cgi->param($_)));
+ } ( fields('svc_broadband'), qw( pkgnum svcpart ) )
+} );
+
+unless ( $new->ip_addr ) {
+ $new->ip_addr(join('.', (map $cgi->param('ip_addr_'.$_), (a..d))));
+}
+
+unless ( $new->mac_addr) {
+ $new->mac_addr(join(':', (map $cgi->param('mac_addr_'.$_), (a..f))));
+}
+
+my $error;
+if ( $svcnum ) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $svcnum = $new->svcnum;
+}
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ $cgi->param('ip_addr', $new->ip_addr);
+ $cgi->param('mac_addr', $new->mac_addr);
+ print $cgi->redirect(popurl(2). "svc_broadband.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "view/svc_broadband.cgi?" . $svcnum );
+}
+
+%>
diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi
new file mode 100644
index 000000000..d8a1f7a2a
--- /dev/null
+++ b/httemplate/edit/svc_broadband.cgi
@@ -0,0 +1,219 @@
+<!-- mason kludge -->
+<%
+
+my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_broadband );
+if ( $cgi->param('error') ) {
+ $svc_broadband = new FS::svc_broadband ( {
+ map { $_, scalar($cgi->param($_)) } fields('svc_broadband')
+ } );
+ $svcnum = $svc_broadband->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $cgi->param('svcpart');
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+} else {
+ my($query) = $cgi->keywords;
+ if ( $query =~ /^(\d+)$/ ) { #editing
+ $svcnum=$1;
+ $svc_broadband=qsearchs('svc_broadband',{'svcnum'=>$svcnum})
+ or die "Unknown (svc_broadband) svcnum!";
+
+ my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
+ or die "Unknown (cust_svc) svcnum!";
+
+ $pkgnum=$cust_svc->pkgnum;
+ $svcpart=$cust_svc->svcpart;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ } else { #adding
+
+ $svc_broadband = new FS::svc_broadband({});
+
+ foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart
+ $pkgnum=$1 if /^pkgnum(\d+)$/;
+ $svcpart=$1 if /^svcpart(\d+)$/;
+ }
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svcnum='';
+
+ #set fixed and default fields from part_svc
+ foreach my $part_svc_column (
+ grep { $_->columnflag } $part_svc->all_part_svc_column
+ ) {
+ $svc_broadband->setfield( $part_svc_column->columnname,
+ $part_svc_column->columnvalue,
+ );
+ }
+
+ }
+}
+my $action = $svc_broadband->svcnum ? 'Edit' : 'Add';
+
+my @ac_list;
+
+if ($pkgnum) {
+
+ unless ($svc_broadband->actypenum) {die "actypenum must be set fixed";};
+ @ac_list = qsearch('ac', { actypenum => $svc_broadband->getfield('actypenum') });
+
+} elsif ( $action eq 'Edit' ) {
+
+ #Nothing?
+
+} else {
+ die "\$action eq Add, but \$pkgnum is null!\n";
+}
+
+
+my $p1 = popurl(1);
+print header("Broadband Service $action", '');
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+print qq!<FORM ACTION="${p1}process/svc_broadband.cgi" METHOD=POST>!;
+
+#display
+
+
+
+#svcnum
+print qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!;
+print qq!Service #<B>!, $svcnum ? $svcnum : "(NEW)", "</B><BR><BR>";
+
+#pkgnum
+print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!;
+
+#svcpart
+print qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!;
+
+#actypenum
+print '<INPUT TYPE="hidden" NAME="actypenum" VALUE="' .
+ $svc_broadband->actypenum . '">';
+
+
+print &ntable("#cccccc",2) . qq!<TR><TD ALIGN="right">AC</TD><TD>!;
+
+#acnum
+if (( $part_svc->part_svc_column('acnum')->columnflag eq 'F' ) or
+ ( !$pkgnum )) {
+
+ my $ac = qsearchs('ac', { acnum => $svc_broadband->acnum });
+ my ($acnum, $acname) = ($ac->acnum, $ac->acname);
+
+ print qq!<INPUT TYPE="hidden" NAME="acnum" VALUE="${acnum}">! .
+ qq!${acnum}: ${acname}</TD></TR>!;
+
+} else {
+
+ my @ac_list = qsearch('ac', { actypenum => $svc_broadband->actypenum });
+ print qq!<SELECT NAME="acnum" SIZE="1"><OPTION VALUE=""></OPTION>!;
+
+ foreach ( @ac_list ) {
+ my ($acnum, $acname) = ($_->acnum, $_->acname);
+ print qq!<OPTION VALUE="${acnum}"! .
+ ($acnum == $svc_broadband->acnum ? ' SELECTED>' : '>') .
+ qq!${acname}</OPTION>!;
+ }
+ print '</TD></TR>';
+
+}
+
+#speed_up & speed_down
+my ($speed_up, $speed_down) = ($svc_broadband->speed_up,
+ $svc_broadband->speed_down);
+
+print '<TR><TD ALIGN="right">Download speed</TD><TD>';
+if ( $part_svc->part_svc_column('speed_down')->columnflag eq 'F' ) {
+ print qq!<INPUT TYPE="hidden" NAME="speed_down" VALUE="${speed_down}">! .
+ qq!${speed_down}Kbps</TD></TR>!;
+} else {
+ print qq!<INPUT TYPE="text" NAME="speed_down" SIZE=5 VALUE="${speed_down}">! .
+ qq!Kbps</TD></TR>!;
+}
+
+print '<TR><TD ALIGN="right">Upload speed</TD><TD>';
+if ( $part_svc->part_svc_column('speed_up')->columnflag eq 'F' ) {
+ print qq!<INPUT TYPE="hidden" NAME="speed_up" VALUE="${speed_up}">! .
+ qq!${speed_up}Kbps</TD></TR>!;
+} else {
+ print qq!<INPUT TYPE="text" NAME="speed_up" SIZE=5 VALUE="${speed_up}">! .
+ qq!Kbps</TD></TR>!;
+}
+
+#ip_addr & ip_netmask
+#We're assuming that ip_netmask is fixed if ip_addr is fixed.
+#If it isn't, well, <shudder> what the heck are you doing!?!?
+
+my ($ip_addr, $ip_netmask) = ($svc_broadband->ip_addr,
+ $svc_broadband->ip_netmask);
+
+print '<TR><TD ALIGN="right">IP address/Mask</TD><TD>';
+if ( $part_svc->part_svc_column('ip_addr')->columnflag eq 'F' ) {
+ print qq!<INPUT TYPE="hidden" NAME="ip_addr" VALUE="${ip_addr}">! .
+ qq!<INPUT TYPE="hidden" NAME="ip_netmask" VALUE="${ip_netmask}">! .
+ qq!${ip_addr}/${ip_netmask}</TD></TR>!;
+} else {
+ $ip_addr =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
+ print <<END;
+ <INPUT TYPE="text" NAME="ip_addr_a" SIZE="3" MAXLENGTH="3" VALUE="${1}">.
+ <INPUT TYPE="text" NAME="ip_addr_b" SIZE="3" MAXLENGTH="3" VALUE="${2}">.
+ <INPUT TYPE="text" NAME="ip_addr_c" SIZE="3" MAXLENGTH="3" VALUE="${3}">.
+ <INPUT TYPE="text" NAME="ip_addr_d" SIZE="3" MAXLENGTH="3" VALUE="${4}">/
+ <INPUT TYPE="text" NAME="ip_netmask" SIZE="2" MAXLENGTH="2" VALUE="${ip_netmask}">
+</TD></TR>
+<TR><TD COLSPAN="2" WIDTH="300">
+<P><SMALL>Leave the IP address and netmask blank for automatic assignment of a /32 address. Specifing the netmask and not the address will force assignment of a larger block.</SMALL></P>
+</TD></TR>
+END
+}
+
+#mac_addr
+my $mac_addr = $svc_broadband->mac_addr;
+
+unless (( $part_svc->part_svc_column('mac_addr')->columnflag eq 'F' ) and
+ ( $mac_addr eq '' )) {
+ print '<TR><TD ALIGN="right">MAC Address</TD><TD>';
+ if ( $part_svc->part_svc_column('mac_addr')->columnflag eq 'F' ) { #Why?
+ print qq!<INPUT TYPE="hidden" NAME="mac_addr" VALUE="${mac_addr}">! .
+ qq!${mac_addr}</TD></TR>!;
+ } else {
+ #Ewwww
+ $mac_addr =~ /^([a-f0-9]{2}):([a-f0-9]{2}):([a-f0-9]{2}):([a-f0-9]{2}):([a-f0-9]{2}):([a-f0-9]{2})$/i;
+ print <<END;
+ <INPUT TYPE="text" NAME="mac_addr_a" SIZE="2" MACLENGTH="2" VALUE="${1}">:
+ <INPUT TYPE="text" NAME="mac_addr_b" SIZE="2" MACLENGTH="2" VALUE="${2}">:
+ <INPUT TYPE="text" NAME="mac_addr_c" SIZE="2" MACLENGTH="2" VALUE="${3}">:
+ <INPUT TYPE="text" NAME="mac_addr_d" SIZE="2" MACLENGTH="2" VALUE="${4}">:
+ <INPUT TYPE="text" NAME="mac_addr_e" SIZE="2" MACLENGTH="2" VALUE="${5}">:
+ <INPUT TYPE="text" NAME="mac_addr_f" SIZE="2" MACLENGTH="2" VALUE="${6}">
+</TD></TR>
+END
+
+ }
+}
+
+#location
+my $location = $svc_broadband->location;
+
+print '<TR><TD VALIGN="top" ALIGN="right">Location</TD><TD BGCOLOR="#e8e8e8">';
+if ( $part_svc->part_svc_column('location')->columnflag eq 'F' ) {
+ print qq!<PRE>${location}</PRE></TD></TR>!;
+} else {
+ print qq!<TEXTAREA ROWS="4" COLS="30" NAME="location">${location}</TEXTAREA>!;
+}
+
+print '</TABLE><BR><INPUT TYPE="submit" VALUE="Submit">';
+
+print <<END;
+
+ </FORM>
+ </BODY>
+</HTML>
+END
+%>
diff --git a/httemplate/index.html b/httemplate/index.html
index dce020b1b..2cb707326 100644
--- a/httemplate/index.html
+++ b/httemplate/index.html
@@ -194,6 +194,10 @@
into counties and assign different tax rates to each.
<LI><A HREF="browse/svc_acct_pop.cgi">View/Edit Access Numbers</A>
- Points of Presence
+ <LI><A HREF="browse/ac_type.cgi">View/Edit AC Types</A>
+ - Broadband service access concentrator types.
+ <LI><A HREF="browse/ac.cgi">View/Edit AC</A>
+ - Broadband service access concentrators.
<LI><A HREF="browse/part_bill_event.cgi">View/Edit invoice events</A> - Actions for overdue invoices
<LI><A HREF="browse/msgcat.cgi">View/Edit message catalog</A> - Change error messages and other customizable labels.
</ul>
diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi
new file mode 100644
index 000000000..156edfaec
--- /dev/null
+++ b/httemplate/view/svc_broadband.cgi
@@ -0,0 +1,75 @@
+<!-- mason kludge -->
+<%
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_broadband = qsearchs( 'svc_broadband', { 'svcnum' => $svcnum } )
+ or die "svc_broadband: Unknown svcnum $svcnum";
+
+#false laziness w/all svc_*.cgi
+my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } );
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum);
+if ($pkgnum) {
+ $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+ $custnum = $cust_pkg->custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+#eofalse
+
+my $ac = qsearchs('ac', { acnum => $svc_broadband->getfield('acnum') });
+
+my (
+ $acname,
+ $acnum,
+ $speed_down,
+ $speed_up,
+ $ip_addr,
+ $ip_netmask,
+ $mac_addr,
+ $location
+ ) = (
+ $ac->getfield('acname'),
+ $ac->getfield('acnum'),
+ $svc_broadband->getfield('speed_down'),
+ $svc_broadband->getfield('speed_up'),
+ $svc_broadband->getfield('ip_addr'),
+ $svc_broadband->getfield('ip_netmask'),
+ $svc_broadband->getfield('mac_addr'),
+ $svc_broadband->getfield('location')
+ );
+
+print header('Broadband Service View', menubar(
+ ( ( $custnum )
+ ? ( "View this package (#$pkgnum)" => "${p}view/cust_pkg.cgi?$pkgnum",
+ "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ( "Cancel this (unaudited) website" =>
+ "${p}misc/cancel-unaudited.cgi?$svcnum" )
+ ),
+ "Main menu" => $p,
+)).
+ qq!<A HREF="${p}edit/svc_broadband.cgi?$svcnum">Edit this information</A><BR>!.
+ ntable("#cccccc"). '<TR><TD>'. ntable("#cccccc",2).
+ qq!<TR><TD ALIGN="right">Service number</TD>!.
+ qq!<TD BGCOLOR="#ffffff">$svcnum</TD></TR>!.
+ qq!<TR><TD ALIGN="right">AC</TD>!.
+ qq!<TD BGCOLOR="#ffffff">$acnum: $acname</TD></TR>!.
+ qq!<TR><TD ALIGN="right">Download Speed</TD>!.
+ qq!<TD BGCOLOR="#ffffff">$speed_down</TD></TR>!.
+ qq!<TR><TD ALIGN="right">Upload Speed</TD>!.
+ qq!<TD BGCOLOR="#ffffff">$speed_up</TD></TR>!.
+ qq!<TR><TD ALIGN="right">IP Address/Mask</TD>!.
+ qq!<TD BGCOLOR="#ffffff">$ip_addr/$ip_netmask</TD></TR>!.
+ qq!<TR><TD ALIGN="right">MAC Address</TD>!.
+ qq!<TD BGCOLOR="#ffffff">$mac_addr</TD></TR>!.
+ qq!<TR><TD ALIGN="right" VALIGN="TOP">Location</TD>!.
+ qq!<TD BGCOLOR="#ffffff"><PRE>$location</PRE></TD></TR>!.
+ '</TABLE></TD></TR></TABLE>'.
+ '<BR>'. joblisting({'svcnum'=>$svcnum}, 1).
+ '</BODY></HTML>'
+;
+%>