diff options
author | khoff <khoff> | 2002-09-09 23:05:30 +0000 |
---|---|---|
committer | khoff <khoff> | 2002-09-09 23:05:30 +0000 |
commit | 44398c83f25bf4e43838df9f39331c29fdeff19d (patch) | |
tree | 963837373a3b621ee2140adad0eb0b44e12c75e6 | |
parent | 91292eadb6254740a9b72e5dc95f575593f6a35d (diff) |
svc_broadband merge
-rw-r--r-- | FS/FS/ac.pm | 148 | ||||
-rwxr-xr-x | FS/FS/ac_block.pm | 148 | ||||
-rwxr-xr-x | FS/FS/ac_field.pm | 138 | ||||
-rwxr-xr-x | FS/FS/ac_type.pm | 128 | ||||
-rw-r--r-- | FS/FS/cust_svc.pm | 4 | ||||
-rwxr-xr-x | FS/FS/part_ac_field.pm | 102 | ||||
-rw-r--r-- | FS/FS/part_export.pm | 3 | ||||
-rwxr-xr-x | FS/FS/svc_broadband.pm | 295 | ||||
-rwxr-xr-x | bin/fs-setup | 72 | ||||
-rw-r--r-- | htetc/global.asa | 6 | ||||
-rwxr-xr-x | httemplate/browse/ac.cgi | 57 | ||||
-rwxr-xr-x | httemplate/browse/ac_type.cgi | 47 | ||||
-rwxr-xr-x | httemplate/edit/ac.cgi | 163 | ||||
-rwxr-xr-x | httemplate/edit/ac_type.cgi | 106 | ||||
-rwxr-xr-x | httemplate/edit/part_svc.cgi | 13 | ||||
-rwxr-xr-x | httemplate/edit/process/ac.cgi | 28 | ||||
-rwxr-xr-x | httemplate/edit/process/ac_block.cgi | 21 | ||||
-rwxr-xr-x | httemplate/edit/process/ac_field.cgi | 21 | ||||
-rwxr-xr-x | httemplate/edit/process/ac_type.cgi | 28 | ||||
-rwxr-xr-x | httemplate/edit/process/part_ac_field.cgi | 21 | ||||
-rwxr-xr-x | httemplate/edit/process/part_svc.cgi | 2 | ||||
-rw-r--r-- | httemplate/edit/process/svc_broadband.cgi | 45 | ||||
-rw-r--r-- | httemplate/edit/svc_broadband.cgi | 219 | ||||
-rw-r--r-- | httemplate/index.html | 4 | ||||
-rw-r--r-- | httemplate/view/svc_broadband.cgi | 75 |
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>' +; +%> |