From 07efe8b9d3726845228538df46fcfa2610f89eb8 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 14 Jan 2003 10:15:26 +0000 Subject: selfservice --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index da56c7342..3362ca832 100644 --- a/Makefile +++ b/Makefile @@ -137,6 +137,8 @@ install-init: s/%%%SIGNUP_MACHINE%%%/${SIGNUP_MACHINE}/g;\ s/%%%SIGNUP_AGENTNUM%%%/${SIGNUP_AGENTNUM}/g;\ s/%%%SIGNUP_REFNUM%%%/${SIGNUP_REFNUM}/g;\ + s/%%%SELFSERVICE_USER%%%/${SELFSERVICE_USER}/g;\ + s/%%%SELFSERVICE_MACHINE%%%/${SELFSERVICE_MACHINE}/g;\ " ${INIT_FILE} install: install-perl-modules install-docs install-init -- cgit v1.2.1 From 2485346165b7ebb0e6d2052baa1b03546d44bece Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 16 Jan 2003 02:58:22 +0000 Subject: don't run empty shellcommands --- FS/FS/part_export/shellcommands.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm index dbd4017f8..f592a838a 100644 --- a/FS/FS/part_export/shellcommands.pm +++ b/FS/FS/part_export/shellcommands.pm @@ -33,6 +33,7 @@ sub _export_unsuspend { sub _export_command { my ( $self, $action, $svc_acct) = (shift, shift, shift); my $command = $self->option($action); + return '' if $command =~ /^\s*$/; my $stdin = $self->option($action."_stdin"); no strict 'vars'; -- cgit v1.2.1 From e5e992881cee6c8bb5c64c101d1985d47fd62cd6 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 17 Jan 2003 06:21:38 +0000 Subject: selfservice cancel functionality --- FS/FS/ClientAPI/MyAccount.pm | 20 ++++++++++++++++++++ fs_selfservice/FS-SelfService/SelfService.pm | 1 + 2 files changed, 21 insertions(+) diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 674785524..9983b5d26 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -18,6 +18,7 @@ FS::ClientAPI->register_handlers( 'MyAccount/login' => \&login, 'MyAccount/customer_info' => \&customer_info, 'MyAccount/invoice' => \&invoice, + 'MyAccount/cancel' => \&cancel, ); #store in db? @@ -133,4 +134,23 @@ sub invoice { } +sub cancel { + my $p = shift; + my $session = $cache->get($p->{'session_id'}) + or return { 'error' => "Can't resume session" }; #better error message + + my $custnum = $session->{'custnum'}; + + my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) + or return { 'error' => "unknown custnum $custnum" }; + + my @errors = $cust_main->cancel; + + my $error = scalar(@errors) ? join(' / ', @errors) : ''; + + return { 'error' => $error }; + +} + +1; diff --git a/fs_selfservice/FS-SelfService/SelfService.pm b/fs_selfservice/FS-SelfService/SelfService.pm index 9019ea4f8..4d68d614a 100644 --- a/fs_selfservice/FS-SelfService/SelfService.pm +++ b/fs_selfservice/FS-SelfService/SelfService.pm @@ -22,6 +22,7 @@ $socket = "/usr/local/freeside/selfservice_socket"; 'login' => 'MyAccount/login', 'customer_info' => 'MyAccount/customer_info', 'invoice' => 'MyAccount/invoice', + 'cancel' => 'MyAccount/cancel', ); @EXPORT_OK = keys %autoload; -- cgit v1.2.1 From 561c9be9d086604225956da6cfdf9a9050176174 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 28 Jan 2003 06:08:39 +0000 Subject: crypt password export to ldap fix from dave denney --- FS/FS/part_export/ldap.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/part_export/ldap.pm b/FS/FS/part_export/ldap.pm index fd7f11d90..57fd1f3f4 100644 --- a/FS/FS/part_export/ldap.pm +++ b/FS/FS/part_export/ldap.pm @@ -25,7 +25,7 @@ sub _export_insert { } } $crypt_password = ''; #surpress "used only once" warnings - $crypt_password = crypt( $svc_acct->_password, + $crypt_password = '{crypt}'. crypt( $svc_acct->_password, $saltset[int(rand(64))].$saltset[int(rand(64))] ); my $username_attrib; -- cgit v1.2.1 From 39b71146259e7c2eab3b0c3326000087a7dd75b1 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 28 Jan 2003 07:47:52 +0000 Subject: eek, and this is what caused connectup to fail too --- FS/bin/freeside-setup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/bin/freeside-setup b/FS/bin/freeside-setup index 8b7466222..0ef3fc81b 100755 --- a/FS/bin/freeside-setup +++ b/FS/bin/freeside-setup @@ -669,7 +669,7 @@ sub tables_hash_hack { ], 'primary_key' => 'pkgpart', 'unique' => [], - 'index' => [ [ disabled ], ], + 'index' => [ [ 'disabled' ], ], }, # 'part_title' => { -- cgit v1.2.1 From 66729728e57c13e44eaac51dc8deaf37e47b7530 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 4 Feb 2003 22:04:31 +0000 Subject: 1.5-specific fixes --- htetc/global.asa | 1 - httemplate/docs/install.html | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/htetc/global.asa b/htetc/global.asa index a2d8dee12..7d84600ed 100644 --- a/htetc/global.asa +++ b/htetc/global.asa @@ -48,7 +48,6 @@ use FS::raddb; use FS::session; use FS::svc_acct; use FS::svc_acct_pop qw(popselector); -use FS::svc_acct_sm; use FS::svc_domain; use FS::svc_forward; use FS::svc_www; diff --git a/httemplate/docs/install.html b/httemplate/docs/install.html index 36b8ae0e7..51dce4da5 100644 --- a/httemplate/docs/install.html +++ b/httemplate/docs/install.html @@ -54,6 +54,7 @@ Before installing, you need:
  • Storable
  • NetAddr-IP +
  • Chart
  • Apache::DBI (optional but recommended for better webinterface performance) -- cgit v1.2.1 From c89aa83639038cc1946fec07a2dda252f64e5144 Mon Sep 17 00:00:00 2001 From: khoff Date: Wed, 5 Feb 2003 22:06:14 +0000 Subject: ip_netmask is gone now --- FS/FS/cust_svc.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm index 66daec17b..5117eff45 100644 --- a/FS/FS/cust_svc.pm +++ b/FS/FS/cust_svc.pm @@ -292,7 +292,7 @@ sub label { 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; + $tag = $svc_x->ip_addr; } else { cluck "warning: asked for label of unsupported svcdb; using svcnum"; $tag = $svc_x->getfield('svcnum'); -- cgit v1.2.1 From 0354f39ed0e74fd2eae1d9da13906625b4f56591 Mon Sep 17 00:00:00 2001 From: khoff Date: Wed, 5 Feb 2003 23:17:17 +0000 Subject: svc_broadband rewrite --- FS/FS/addr_block.pm | 322 ++++++++++++++++++++++ FS/FS/part_router_field.pm | 134 +++++++++ FS/FS/part_sb_field.pm | 267 ++++++++++++++++++ FS/FS/part_svc_router.pm | 32 +++ FS/FS/router.pm | 156 +++++++++++ FS/FS/router_field.pm | 146 ++++++++++ FS/FS/sb_field.pm | 148 ++++++++++ FS/FS/svc_broadband.pm | 225 ++++++++------- FS/bin/freeside-setup | 89 +++--- htetc/global.asa | 12 +- htetc/handler.pl | 8 + httemplate/browse/addr_block.cgi | 76 +++++ httemplate/browse/generic.cgi | 46 ++++ httemplate/browse/part_sb_field.cgi | 31 +++ httemplate/browse/router.cgi | 37 +++ httemplate/edit/part_router_field.cgi | 70 +++++ httemplate/edit/part_sb_field.cgi | 79 ++++++ httemplate/edit/process/addr_block/add.cgi | 20 ++ httemplate/edit/process/addr_block/allocate.cgi | 25 ++ httemplate/edit/process/addr_block/deallocate.cgi | 24 ++ httemplate/edit/process/addr_block/split.cgi | 19 ++ httemplate/edit/process/generic.cgi | 69 +++++ httemplate/edit/process/router.cgi | 100 +++++++ httemplate/edit/process/svc_broadband.cgi | 54 +++- httemplate/edit/router.cgi | 88 ++++++ httemplate/edit/svc_broadband.cgi | 251 ++++++++--------- httemplate/index.html | 14 +- httemplate/view/svc_broadband.cgi | 62 +++-- 28 files changed, 2269 insertions(+), 335 deletions(-) create mode 100755 FS/FS/addr_block.pm create mode 100755 FS/FS/part_router_field.pm create mode 100755 FS/FS/part_sb_field.pm create mode 100755 FS/FS/part_svc_router.pm create mode 100755 FS/FS/router.pm create mode 100755 FS/FS/router_field.pm create mode 100755 FS/FS/sb_field.pm create mode 100644 httemplate/browse/addr_block.cgi create mode 100644 httemplate/browse/generic.cgi create mode 100644 httemplate/browse/part_sb_field.cgi create mode 100644 httemplate/browse/router.cgi create mode 100644 httemplate/edit/part_router_field.cgi create mode 100644 httemplate/edit/part_sb_field.cgi create mode 100755 httemplate/edit/process/addr_block/add.cgi create mode 100755 httemplate/edit/process/addr_block/allocate.cgi create mode 100755 httemplate/edit/process/addr_block/deallocate.cgi create mode 100755 httemplate/edit/process/addr_block/split.cgi create mode 100644 httemplate/edit/process/generic.cgi create mode 100644 httemplate/edit/process/router.cgi create mode 100755 httemplate/edit/router.cgi diff --git a/FS/FS/addr_block.pm b/FS/FS/addr_block.pm new file mode 100755 index 000000000..b671723aa --- /dev/null +++ b/FS/FS/addr_block.pm @@ -0,0 +1,322 @@ +package FS::addr_block; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearchs qsearch dbh ); +use FS::router; +use FS::svc_broadband; +use NetAddr::IP; + +@ISA = qw( FS::Record ); + +=head1 NAME + +FS::addr_block - Object methods for addr_block records + +=head1 SYNOPSIS + + use FS::addr_block; + + $record = new FS::addr_block \%hash; + $record = new FS::addr_block { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::addr_block record describes an address block assigned for broadband +access. FS::addr_block inherits from FS::Record. The following fields are +currently supported: + +=over 4 + +=item blocknum - primary key, used in FS::svc_broadband to associate +services to the block. + +=item routernum - the router (see FS::router) to which this +block is assigned. + +=item ip_gateway - the gateway address used by customers within this block. + +=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 "insert". + +=cut + +sub table { 'addr_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. + +sub delete { + my $self = shift; + return 'Block must be deallocated before deletion' + if $self->router; + + $self->SUPER::delete; +} + +=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('routernum') + || $self->ut_ip('ip_gateway') + || $self->ut_number('ip_netmask') + ; + return $error if $error; + + + # A routernum of 0 indicates an unassigned block and is allowed + return "Unknown routernum" + if ($self->routernum and not $self->router); + + my $self_addr = $self->NetAddr; + return "Cannot parse address: ". $self->ip_gateway . '/' . $self->ip_netmask + unless $self_addr; + + if (not $self->blocknum) { + my @block = grep { + my $block_addr = $_->NetAddr; + if($block_addr->contains($self_addr) + or $self_addr->contains($block_addr)) { $_; }; + } qsearch( 'addr_block', {}); + foreach(@block) { + return "Block intersects existing block ".$_->ip_gateway."/".$_->ip_netmask; + } + } + + ''; +} + + +=item router + +Returns the FS::router object corresponding to this object. If the +block is unassigned, returns undef. + +=cut + +sub router { + my $self = shift; + return qsearchs('router', { routernum => $self->routernum }); +} + +=item svc_broadband + +Returns a list of FS::svc_broadband objects associated +with this object. + +=cut + +sub svc_broadband { + my $self = shift; + return qsearch('svc_broadband', { blocknum => $self->blocknum }); +} + +=item NetAddr + +Returns a NetAddr::IP object for this block's address and netmask. + +=cut + +sub NetAddr { + my $self = shift; + + return new NetAddr::IP ($self->ip_gateway, $self->ip_netmask); +} + +=item next_free_addr + +Returns a NetAddr::IP object corresponding to the first unassigned address +in the block (other than the network, broadcast, or gateway address). If +there are no free addresses, returns false. + +=cut + +sub next_free_addr { + my $self = shift; + + my @used = map { $_->NetAddr->addr } + ($self, + qsearch('svc_broadband', { blocknum => $self->blocknum }) ); + + my @free = $self->NetAddr->hostenum; + while (my $ip = shift @free) { + if (not grep {$_ eq $ip->addr;} @used) { return $ip; }; + } + + ''; + +} + +=item allocate + +Allocates this address block to a router. Takes an FS::router object +as an argument. + +At present it's not possible to reallocate a block to a different router +except by deallocating it first, which requires that none of its addresses +be assigned. This is probably as it should be. + +=cut + +sub allocate { + my ($self, $router) = @_; + + return 'Block is already allocated' + if($self->router); + + return 'Block must be allocated to a router' + unless(ref $router eq 'FS::router'); + + my @svc = $self->svc_broadband; + if (@svc) { + return 'Block has assigned addresses: '. join ', ', map {$_->ip_addr} @svc; + } + + my $new = new FS::addr_block {$self->hash}; + $new->routernum($router->routernum); + return $new->replace($self); + +} + +=item deallocate + +Deallocates the block (i.e. sets the routernum to 0). If any addresses in the +block are assigned to services, it fails. + +=cut + +sub deallocate { + my $self = shift; + + my @svc = $self->svc_broadband; + if (@svc) { + return 'Block has assigned addresses: '. join ', ', map {$_->ip_addr} @svc; + } + + my $new = new FS::addr_block {$self->hash}; + $new->routernum(0); + return $new->replace($self); +} + +=item split_block + +Splits this address block into two equal blocks, occupying the same space as +the original block. The first of the two will also have the same blocknum. +The gateway address of each block will be set to the first usable address, i.e. +(network address)+1. Since this method is designed for use on unallocated +blocks, this is probably the correct behavior. + +(At present, splitting allocated blocks is disallowed. Anyone who wants to +implement this is reminded that each split costs three addresses, and any +customers who were using these addresses will have to be moved; depending on +how full the block was before being split, they might have to be moved to a +different block. Anyone who I wants to implement it is asked to tie it +to a configuration switch so that site admins can disallow it.) + +=cut + +sub split_block { + + # We should consider using Attribute::Handlers/Aspect/Hook::LexWrap/ + # something to atomicize functions, so that we can say + # + # sub split_block : atomic { + # + # instead of repeating all this AutoCommit verbage in every + # sub that does more than one database operation. + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $self = shift; + my $error; + + if ($self->router) { + return 'Block is already allocated'; + } + + #TODO: Smallest allowed block should be a config option. + if ($self->NetAddr->masklen() ge 30) { + return 'Cannot split blocks with a mask length >= 30'; + } + + my (@new, @ip); + $ip[0] = $self->NetAddr; + @ip = map {$_->first()} $ip[0]->split($self->ip_netmask + 1); + + foreach (0,1) { + $new[$_] = new FS::addr_block {$self->hash}; + $new[$_]->ip_gateway($ip[$_]->addr); + $new[$_]->ip_netmask($ip[$_]->masklen); + } + + $new[1]->blocknum(''); + + $error = $new[0]->replace($self); + if ($error) { + $dbh->rollback; + return $error; + } + + $error = $new[1]->insert; + if ($error) { + $dbh->rollback; + return $error; + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + return ''; +} + +=item merge + +To be implemented. + +=back + +=head1 BUGS + +Minimum block size should be a config option. It's hardcoded at /30 right +now because that's the smallest block that makes any sense at all. + +1; + diff --git a/FS/FS/part_router_field.pm b/FS/FS/part_router_field.pm new file mode 100755 index 000000000..73ca50fb6 --- /dev/null +++ b/FS/FS/part_router_field.pm @@ -0,0 +1,134 @@ +package FS::part_router_field; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearchs ); +use FS::router_field; +use FS::router; + + +@ISA = qw( FS::Record ); + +=head1 NAME + +FS::part_router_field - Object methods for part_router_field records + +=head1 SYNOPSIS + + use FS::part_router_field; + + $record = new FS::part_router_field \%hash; + $record = new FS::part_router_field { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +A part_router_field represents an xfield definition for routers. For more +information on xfields, see L. + +The following fields are supported: + +=over 4 + +=item routerfieldpart - primary key (assigned automatically) + +=item name - name of field + +=item length + +=item check_block + +=item list_source + +(See L for details on these fields.) + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Create a new record. To add the record to the database, see "insert". + +=cut + +sub table { 'part_router_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_router_field"; + + ''; #no error +} + +=item list_values + +Equivalent to "eval($part_router_field->list_source)". + +=cut + +sub list_values { + my $self = shift; + return () unless $self->list_source; + my @opts = eval($self->list_source); + if($@) { + warn $@; + return (); + } else { + return @opts; + } +} + +=back + +=head1 VERSION + +$Id: + +=head1 BUGS + +Needless duplication of much of FS::part_sb_field, with the result that most of +the warnings about it apply here also. + +=head1 SEE ALSO + +FS::svc_broadband, FS::router, FS::router_field, schema.html +from the base documentation. + +=cut + +1; + diff --git a/FS/FS/part_sb_field.pm b/FS/FS/part_sb_field.pm new file mode 100755 index 000000000..8dca946b5 --- /dev/null +++ b/FS/FS/part_sb_field.pm @@ -0,0 +1,267 @@ +package FS::part_sb_field; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearchs ); + +@ISA = qw( FS::Record ); + +=head1 NAME + +FS::part_sb_field - Object methods for part_sb_field records + +=head1 SYNOPSIS + + use FS::part_sb_field; + + $record = new FS::part_sb_field \%hash; + $record = new FS::part_sb_field { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::part_sb_field object represents an extended field (xfield) definition +for svc_broadband's sb_field mechanism (see L). +FS::part_sb_field inherits from FS::Record. The following fields are +currently supported: + +=over 2 + +=item sbfieldpart - primary key (assigned automatically) + +=item name - name of the field + +=item svcpart - service type for which this field is available (see L) + +=item length - length of the contents of the field (see note #1) + +=item check_block - validation routine (see note #2) + +=item list_source - enumeration routine (see note #3) + +=back + +=head1 BACKGROUND + +Broadband services, unlike dialup services, are provided over a wide +variety of physical media (DSL, wireless, cable modems, digital circuits) +and network architectures (Ethernet, PPP, ATM). For many of these access +mechanisms, adding a new customer requires knowledge of some properties +of the physical connection (circuit number, the type of CPE in use, etc.). +It is unreasonable to expect ISPs to alter Freeside's schema (and the +associated library and UI code) to make each of these parameters a field in +svc_broadband. + +Hence sb_field and part_sb_field. They allow the Freeside administrator to +define 'extended fields' ('xfields') associated with svc_broadband records. +These are I processed in any way by Freeside itself; they exist solely for +use by exports (see L) and technical support staff. + +For a parallel mechanism (at the per-router level rather than per-service), +see L. + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Create a new record. To add the record to the database, see "insert". + +=cut + +sub table { 'part_sb_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 = ''; + + $error = $self->ut_numbern('svcpart'); + return $error if $error; + + unless (qsearchs('part_svc', { svcpart => $self->svcpart })) + { return "Unknown svcpart: " . $self->svcpart;} + + $self->name =~ /^([a-z0-9_\-\.]{1,15})$/i + or return "Invalid field name for part_sb_field"; + + #How to check input_block, display_block, and check_block? + + ''; #no error +} + +=item list_values + +If the I field is set, this method eval()s it and +returns its output. If the field is empty, list_values returns +an empty list. + +Any arguments passed to this method will be received by the list_source +code, but this behavior is a fortuitous accident and may be removed in +the future. + +=cut + +sub list_values { + my $self = shift; + return () unless $self->list_source; + + my @opts = eval($self->list_source); + if($@) { + warn $@; + return (); + } else { + return @opts; + } +} + +=item part_svc + +Returns the FS::part_svc object associated with this field definition. + +=cut + +sub part_svc { + my $self = shift; + return qsearchs('part_svc', { svcpart => $self->svcpart }); +} + +=back + +=head1 VERSION + +$Id: + +=head1 NOTES + +=over + +=item 1. + +The I field is not enforced. It provides a hint to UI +code about how to display the field on a form. If you want to enforce a +minimum or maximum length for a field, use a I. + +=item 2. + +The check_block mechanism used here as well as in +FS::part_router_field allows the user to define validation rules. + +When FS::sb_field::check is called, the proposed value of the xfield is +assigned to $_. The check_block is then eval()'d and its return value +captured. If the return value is false (empty/zero/undef), $_ is then assigned +back into the field and stored in the database. + +Therefore a check_block can do three different things with the value: allow +it, allow it with a modification, or reject it. This is very flexible, but +somewhat dangerous. Some warnings: + +=over 2 + +=item * + +Assume that $_ has had I error checking prior to the +check_block. That's what the check_block is for, after all. It could +contain I: evil shell commands in backquotes, 100kb JPEG images, +the Klez virus, whatever. + +=item * + +If your check_block modifies the input value, it should probably +produce a value that wouldn't be modified by going through the same +check_block again. (That is, it should map input values into its own +eigenspace.) The reason is that if someone calls $new->replace($old), +where $new and $old contain the same value for the field, they probably +want the field to keep its old value, not to get transformed by the +check_block again. So don't do silly things like '$_++' or +'tr/A-Za-z/a-zA-Z/'. + +=item * + +Don't alter the contents of the database. I the database +is perfectly reasonable, but writing to it is a bad idea. Remember that +check() might get called more than once, as described above. + +=item * + +The check_block probably won't even get called if the user submits +an I sb_field. So at present, you can't set up a default value with +something like 's/^$/foo/'. Conversely, don't replace the submitted value +with an empty string. It probably will get stored, but might be deleted at +any time. + +=back + +=item 3. + +The list_source mechanism is a UI hint (like length) to generate +drop-down or list boxes. If list_source contains a value, the UI code can +eval() it and use the results as the options on the list. + +Note 'can'. This is not a substitute for check_block. The HTML interface +currently requires that the user pick one of the options on the list +because that's the way HTML drop-down boxes work, but in the future the UI +code might add an 'Other (please specify)' option and a text box so that +the user can enter something else. Or it might ignore list_source and just +generate a text box. Or the interface might be rewritten in MS Access, +where drop-down boxes have text boxes built in. Data validation is the job +of check(), not the front end. + +Note also that a list of literals evaluates to itself, so a list_source +like + +C<('Windows', 'MacOS', 'Linux')> + +or + +C + +means exactly what you'd think. + +=head1 BUGS + +The lack of any way to do default values. We might add this as another UI +hint (since, for the most part, it's the UI's job to figure out which fields +have had values entered into them). In fact, there are lots of things we +should add as UI hints. + +Oh, and the documentation is probably full of lies. + +=head1 SEE ALSO + +FS::svc_broadband, FS::sb_field, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/part_svc_router.pm b/FS/FS/part_svc_router.pm new file mode 100755 index 000000000..0b23ab580 --- /dev/null +++ b/FS/FS/part_svc_router.pm @@ -0,0 +1,32 @@ +package FS::part_svc_router; + +use strict; +use vars qw( @ISA ); +use FS::Record qw(qsearchs); +use FS::router; +use FS::part_svc; + +@ISA = qw(FS::Record); + +sub table { 'part_svc_router'; } + +sub check { + my $self = shift; + my $error = + $self->ut_foreign_key('svcpart', 'part_svc', 'svcpart') + || $self->ut_foreign_key('routernum', 'router', 'routernum'); + return $error if $error; + ''; #no error +} + +sub router { + my $self = shift; + return qsearchs('router', { routernum => $self->routernum }); +} + +sub part_svc { + my $self = shift; + return qsearchs('part_svc', { svcpart => $self->svcpart }); +} + +1; diff --git a/FS/FS/router.pm b/FS/FS/router.pm new file mode 100755 index 000000000..3f9459a01 --- /dev/null +++ b/FS/FS/router.pm @@ -0,0 +1,156 @@ +package FS::router; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearchs qsearch ); +use FS::addr_block; + +@ISA = qw( FS::Record ); + +=head1 NAME + +FS::router - Object methods for router records + +=head1 SYNOPSIS + + use FS::router; + + $record = new FS::router \%hash; + $record = new FS::router { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::router record describes a broadband router, such as a DSLAM or a wireless + access point. FS::router inherits from FS::Record. The following +fields are currently supported: + +=over 4 + +=item routernum - primary key + +=item routername - descriptive name for the router + +=item svcnum - svcnum of the owning FS::svc_broadband, if appropriate + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Create a new record. To add the record to the database, see "insert". + +=cut + +sub table { 'router'; } + +=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('routernum') + || $self->ut_text('routername'); + return $error if $error; + + ''; +} + +=item addr_block + +Returns a list of FS::addr_block objects (address blocks) associated +with this object. + +=cut + +sub addr_block { + my $self = shift; + return qsearch('addr_block', { routernum => $self->routernum }); +} + +=item router_field + +Returns a list of FS::router_field objects assigned to this object. + +=cut + +sub router_field { + my $self = shift; + + return qsearch('router_field', { routernum => $self->routernum }); +} + +=item part_svc_router + +Returns a list of FS::part_svc_router objects associated with this +object. This is unlikely to be useful for any purpose other than retrieving +the associated FS::part_svc objects. See below. + +=cut + +sub part_svc_router { + my $self = shift; + return qsearch('part_svc_router', { routernum => $self->routernum }); +} + +=item part_svc + +Returns a list of FS::part_svc objects associated with this object. + +=cut + +sub part_svc { + my $self = shift; + return map { qsearchs('part_svc', { svcpart => $_->svcpart }) } + $self->part_svc_router; +} + +=back + +=head1 VERSION + +$Id: + +=head1 BUGS + +=head1 SEE ALSO + +FS::svc_broadband, FS::router, FS::addr_block, FS::router_field, FS::part_svc, +schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/router_field.pm b/FS/FS/router_field.pm new file mode 100755 index 000000000..eee21ab89 --- /dev/null +++ b/FS/FS/router_field.pm @@ -0,0 +1,146 @@ +package FS::router_field; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearchs ); +use FS::part_router_field; +use FS::router; + + +@ISA = qw( FS::Record ); + +=head1 NAME + +FS::router_field - Object methods for router_field records + +=head1 SYNOPSIS + + use FS::router_field; + + $record = new FS::router_field \%hash; + $record = new FS::router_field { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +FS::router_field contains values of router xfields. See FS::part_sb_field +for details on the xfield mechanism. + +=over 4 + +=item routerfieldpart - Type of router_field as defined by +FS::part_router_field + +=item routernum - The FS::router 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 "insert". + +=cut + +sub table { 'router_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 "routernum must be defined" unless $self->routernum; + return "routerfieldpart must be defined" unless $self->routerfieldpart; + + my $part_router_field = $self->part_router_field; + $_ = $self->value; + + my $check_block = $part_router_field->check_block; + if ($check_block) { + $@ = ''; + my $error = (eval($check_block) or $@); + return $error if $error; + $self->setfield('value' => $_); + } + + ''; #no error +} + +=item part_router_field + +Returns a reference to the FS:part_router_field that defines this +FS::router_field + +=cut + +sub part_router_field { + my $self = shift; + + return qsearchs('part_router_field', + { routerfieldpart => $self->routerfieldpart }); +} + +=item router + +Returns a reference to the FS::router to which this FS::router_field +belongs. + +=cut + +sub router { + my $self = shift; + + return qsearchs('router', { routernum => $self->routernum }); +} + +=back + +=head1 VERSION + +$Id: + +=head1 BUGS + +=head1 SEE ALSO + +FS::svc_broadband, FS::router, FS::router_block, FS::router_field, +schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/sb_field.pm b/FS/FS/sb_field.pm new file mode 100755 index 000000000..d4eb37844 --- /dev/null +++ b/FS/FS/sb_field.pm @@ -0,0 +1,148 @@ +package FS::sb_field; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearchs ); +use FS::part_sb_field; + +use UNIVERSAL qw( can ); + +@ISA = qw( FS::Record ); + +=head1 NAME + +FS::sb_field - Object methods for sb_field records + +=head1 SYNOPSIS + + use FS::sb_field; + + $record = new FS::sb_field \%hash; + $record = new FS::sb_field { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +See L for details on this table's mission in life. +FS::sb_field contains the actual values of the xfields defined in +part_sb_field. + +The following fields are supported: + +=over 4 + +=item sbfieldpart - Type of sb_field as defined by FS::part_sb_field + +=item svcnum - The svc_broadband 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 { 'sb_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 the value against the check_block of the corresponding part_sb_field. +Returns whatever the check_block returned (unless the check_block dies, in +which case check returns the die message). Therefore, if the check_block +wants to allow the value to be stored, it must return false. See +L for details. + +=cut + +sub check { + my $self = shift; + + return "svcnum must be defined" unless $self->svcnum; + return "sbfieldpart must be defined" unless $self->sbfieldpart; + + my $part_sb_field = $self->part_sb_field; + + $_ = $self->value; + + my $check_block = $self->part_sb_field->check_block; + if ($check_block) { + $@ = ''; + my $error = (eval($check_block) or $@); # treat fatal errors as errors + return $error if $error; + $self->setfield('value' => $_); + } + + ''; #no error +} + +=item part_sb_field + +Returns a reference to the FS::part_sb_field that defines this FS::sb_field. + +=cut + +sub part_sb_field { + my $self = shift; + + return qsearchs('part_sb_field', { sbfieldpart => $self->sbfieldpart }); +} + +=back + +=item svc_broadband + +Returns a reference to the FS::svc_broadband to which this value is attached. +Nobody's ever going to use this function, but here it is anyway. + +=cut + +sub svc_broadband { + my $self = shift; + + return qsearchs('svc_broadband', { svcnum => $self->svcnum }); +} + +=head1 VERSION + +$Id: + +=head1 BUGS + +=head1 SEE ALSO + +L, schema.html +from the base documentation. + +=cut + +1; + diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index ab92fb3d7..45f6c3601 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -2,10 +2,10 @@ 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 FS::addr_block; use NetAddr::IP; @ISA = qw( FS::svc_Common ); @@ -45,25 +45,6 @@ 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.) - -=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.) - -=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 subclass. - -=back - FS::svc_broadband inherits from FS::svc_Common. The following fields are currently supported: @@ -71,14 +52,7 @@ currently supported: =item svcnum - primary key -=item -actypenum - access concentrator type; see L. 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 blocknum - see FS::addr_block =item speed_up - maximum upload speed, in bits per second. If set to zero, upload @@ -89,28 +63,12 @@ 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 +=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 its 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. - =back =head1 METHODS @@ -120,7 +78,7 @@ that information is stored in L. =item new HASHREF Creates a new svc_broadband. To add the record to the database, see -L<"insert">. +"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 method. @@ -134,14 +92,12 @@ sub table { 'svc_broadband'; } 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) should be +The additional fields pkgnum and svcpart (see 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 @@ -159,19 +115,62 @@ returns the error, otherwise returns false. =cut # Standard FS::svc_Common::replace -# Notice a pattern here? + +=item sb_field + +Returns a list of FS::sb_field objects assigned to this object. + +=cut + +sub sb_field { + my $self = shift; + + return qsearch( 'sb_field', { svcnum => $self->svcnum } ); +} + +=item sb_field_hashref + +Returns a hashref of the FS::sb_field key/value pairs for this object. + +Deprecated. Please don't use it. + +=cut + +# Kristian wrote this, but don't hold it against him. He was under a powerful +# distracting influence whom he evidently found much more interesting than +# svc_broadband.pm. I can't say I blame him. + +sub sb_field_hashref { + my $self = shift; + my $svcpart = shift; + + if ((not $svcpart) && ($self->cust_svc)) { + $svcpart = $self->cust_svc->svcpart; + } + + my $hashref = {}; + + map { + my $sb_field = qsearchs('sb_field', { sbfieldpart => $_->sbfieldpart, + svcnum => $self->svcnum }); + $hashref->{$_->getfield('name')} = $sb_field ? $sb_field->getfield('value') : ''; + } qsearch('part_sb_field', { svcpart => $svcpart }); + + return $hashref; + +} =item suspend -Called by the suspend method of FS::cust_pkg (see L). +Called by the suspend method of FS::cust_pkg (see FS::cust_pkg). =item unsuspend -Called by the unsuspend method of FS::cust_pkg (see L). +Called by the unsuspend method of FS::cust_pkg (see FS::cust_pkg). =item cancel -Called by the cancel method of FS::cust_pkg (see L). +Called by the cancel method of FS::cust_pkg (see FS::cust_pkg). =item check @@ -189,105 +188,99 @@ sub check { my $error = $self->ut_numbern('svcnum') - || $self->ut_foreign_key('actypenum', 'ac_type', 'actypenum') + || $self->ut_foreign_key('blocknum', 'addr_block', 'blocknum') || $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') + || $self->ut_ipn('ip_addr') ; 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; + if (not($self->ip_addr) or $self->ip_addr eq '0.0.0.0') { + $self->ip_addr($self->addr_block->next_free_addr->addr); + if (not $self->ip_addr) { + return "No free addresses in addr_block (blocknum: ".$self->blocknum.")"; + } } - # 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. + # This should catch errors in the ip_addr. If it doesn't, + # they'll almost certainly not map into the block anyway. + my $self_addr = $self->NetAddr; #netmask is /32 + return ('Cannot parse address: ' . $self->ip_addr) unless $self_addr; - 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'; + my $block_addr = $self->addr_block->NetAddr; + unless ($block_addr->contains($self_addr)) { + return 'blocknum '.$self->blocknum.' does not contain address '.$self->ip_addr; } - # 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; - } + my $router = $self->addr_block->router + or return 'Cannot assign address from unallocated block:'.$self->addr_block->blocknum; + if(grep { $_->routernum == $router->routernum} $self->allowed_routers) { + } # do nothing + else { + return 'Router '.$router->routernum.' cannot provide svcpart '.$self->svcpart; } + ''; #no error } -=item ac_block +=item NetAddr -Returns the FS::ac_block record (i.e. the address block) for this broadband service. +Returns a NetAddr::IP object containing the IP address of this service. The netmask +is /32. =cut -sub ac_block { +sub NetAddr { 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 ''; + return new NetAddr::IP ($self->ip_addr); } -=item ac_type +=item addr_block -Returns the FS::ac_type record for this broadband service. +Returns the FS::addr_block record (i.e. the address block) for this broadband service. =cut -sub ac_type { +sub addr_block { my $self = shift; - return qsearchs('ac_type', { actypenum => $self->actypenum }); + + return qsearchs('addr_block', { blocknum => $self->blocknum }); } =back +=item allowed_routers + +Returns a list of allowed FS::router objects. + +=cut + +sub allowed_routers { + my $self = shift; + + return map { $_->router } qsearch('part_svc_router', { svcpart => $self->svcpart }); +} + =head1 BUGS +I think there's one place in the code where we actually use sb_field_hashref. +That's a bug in itself. + +The real problem with it is that we're still grappling with the question of how +tightly xfields should be integrated with real fields. There are a few +different directions we could go with it--we I override several +functions in Record so that xfields behave almost exactly like real fields (can +be set with setfield(), appear in fields() and hash(), used as criteria in +qsearch(), etc.). + =head1 SEE ALSO -L, L, L, L, -L, schema.html from the base documentation. +FS::svc_Common, FS::Record, FS::addr_block, FS::sb_field, +FS::part_svc, schema.html from the base documentation. =cut diff --git a/FS/bin/freeside-setup b/FS/bin/freeside-setup index 0ef3fc81b..19483765e 100755 --- a/FS/bin/freeside-setup +++ b/FS/bin/freeside-setup @@ -1012,76 +1012,99 @@ sub tables_hash_hack { 'index' => [], }, - 'ac_type' => { + 'router' => { 'columns' => [ - 'actypenum', 'serial', '', '', - 'actypename', 'varchar', '', $char_d, + 'routernum', 'serial', '', '', + 'routername', 'varchar', '', $char_d, + 'svcnum', 'int', '0', '', ], - 'primary_key' => 'actypenum', + 'primary_key' => 'routernum', 'unique' => [], 'index' => [], }, - 'ac' => { + 'part_svc_router' => { 'columns' => [ - 'acnum', 'serial', '', '', - 'actypenum', 'int', '', '', - 'acname', 'varchar', '', $char_d, - ], - 'primary_key' => 'acnum', + 'svcpart', 'int', '', '', + 'routernum', 'int', '', '', + ]; + 'primary_key' => '', 'unique' => [], - 'index' => [ [ 'actypenum' ] ], + 'index' => [], }, - 'part_ac_field' => { + 'part_router_field' => { 'columns' => [ - 'acfieldpart', 'serial', '', '', - 'actypenum', 'int', '', '', + 'routerfieldpart', 'serial', '', '', 'name', 'varchar', '', $char_d, - 'ut_type', 'varchar', '', $char_d, + 'length', 'int', '', '', + 'check_block', 'text', 'NULL', '', + 'list_source', 'text', 'NULL', '', ], - 'primary_key' => 'acfieldpart', + 'primary_key' => 'routerfieldpart', 'unique' => [], - 'index' => [ [ 'actypenum' ] ], + 'index' => [], }, - 'ac_field' => { + 'router_field' => { 'columns' => [ - 'acfieldpart', 'int', '', '', - 'acnum', 'int', '', '', - 'value', 'text', '', '', + 'routerfieldpart', 'int', '', '', + 'routernum', 'int', '', '', + 'value', 'varchar', '', 128, ], 'primary_key' => '', - 'unique' => [ [ 'acfieldpart', 'acnum' ] ], - 'index' => [ [ 'acnum' ] ], + 'unique' => [ [ 'routerfieldpart', 'routernum' ] ], + 'index' => [], }, - 'ac_block' => { + 'addr_block' => { 'columns' => [ - 'acnum', 'int', '', '', + 'blocknum', 'int', '', '', + 'routernum', 'int', '', '', 'ip_gateway', 'varchar', '', 15, 'ip_netmask', 'int', '', '', ], + 'primary_key' => 'blocknum', + 'unique' => [ [ 'blocknum', 'routernum' ] ], + 'index' => [], + }, + + 'part_sb_field' => { + 'columns' => [ + 'sbfieldpart', 'int', '', '', + 'svcpart', 'int', '', '', + 'name', 'varchar', '', $char_d, + 'length', 'int', '', '', + 'check_block', 'text', 'NULL', '', + 'list_source', 'text', 'NULL', '', + ], + 'primary_key' => 'sbfieldpart', + 'unique' => [ [ 'sbfieldpart', 'svcpart' ] ], + 'index' => [], + }, + + 'sb_field' => { + 'columns' => [ + 'sbfieldpart', 'int', '', '', + 'svcnum', 'int', '', '', + 'value', 'varchar', '', 128, + ], 'primary_key' => '', - 'unique' => [], - 'index' => [ [ 'acnum' ] ], + 'unique' => [ [ 'sbfieldpart', 'svcnum' ] ], + 'index' => [], }, 'svc_broadband' => { 'columns' => [ 'svcnum', 'int', '', '', - 'actypenum', 'int', '', '', + 'blocknum', 'int', '', '', 'speed_up', 'int', '', '', 'speed_down', 'int', '', '', - 'acnum', 'int', '', '', 'ip_addr', 'varchar', '', 15, - 'ip_netmask', 'int', '', '', - 'mac_addr', 'char', '', 17, - 'location', 'varchar', '', $char_d, ], 'primary_key' => 'svcnum', 'unique' => [], - 'index' => [ [ 'actypenum' ] ], + 'index' => [], }, ); diff --git a/htetc/global.asa b/htetc/global.asa index 7d84600ed..5fd89957f 100644 --- a/htetc/global.asa +++ b/htetc/global.asa @@ -41,6 +41,7 @@ use FS::part_bill_event; use FS::part_pkg; use FS::part_referral; use FS::part_svc; +use FS::part_svc_router; use FS::pkg_svc; use FS::port; use FS::queue qw(joblisting); @@ -51,11 +52,12 @@ use FS::svc_acct_pop qw(popselector); 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::router; +use FS::part_router_field; +use FS::router_field; +use FS::addr_block; +use FS::part_sb_field; +use FS::sb_field; use FS::svc_broadband; use FS::type_pkgs; use FS::part_export; diff --git a/htetc/handler.pl b/htetc/handler.pl index d55ba3310..768ebffec 100644 --- a/htetc/handler.pl +++ b/htetc/handler.pl @@ -94,6 +94,7 @@ sub handler use FS::part_pkg; use FS::part_referral; use FS::part_svc; + use FS::part_svc_router; use FS::pkg_svc; use FS::port; use FS::queue qw(joblisting); @@ -105,6 +106,13 @@ sub handler use FS::svc_domain; use FS::svc_forward; use FS::svc_www; + use FS::router; + use FS::part_router_field; + use FS::router_field; + use FS::addr_block; + use FS::part_sb_field; + use FS::sb_field; + use FS::svc_broadband; use FS::type_pkgs; use FS::part_export; use FS::part_export_option; diff --git a/httemplate/browse/addr_block.cgi b/httemplate/browse/addr_block.cgi new file mode 100644 index 000000000..06ac556cf --- /dev/null +++ b/httemplate/browse/addr_block.cgi @@ -0,0 +1,76 @@ +<%= header('Address Blocks', menubar('Main Menu' => $p)) %> +<% + +use NetAddr::IP; + +my @addr_block = qsearch('addr_block', {}); +my @router = qsearch('router', {}); +my $block; +my $p2 = popurl(2); +my $path = $p2 . "edit/process/addr_block"; + +%> + +<% if ($cgi->param('error')) { %> + Error: <%=$cgi->param('error')%> +

    +<% } %> + +<%=table()%> + +<% foreach $block (sort {$a->NetAddr cmp $b->NetAddr} @addr_block) { %> + + <%=$block->NetAddr%> + <% if (my $router = $block->router) { %> + <% if (scalar($block->svc_broadband) == 0) { %> + + <%=$router->routername%> + + +
    + + +
    + + <% } else { %> + + <%=$router->routername%> + + <% } %> + <% } else { %> + +
    + + + +
    + + +
    + + +
    + + +<% } + } %> +
    + +
    + Gateway/Netmask + + / + + + + +
    + + + + + diff --git a/httemplate/browse/generic.cgi b/httemplate/browse/generic.cgi new file mode 100644 index 000000000..9ac0f2391 --- /dev/null +++ b/httemplate/browse/generic.cgi @@ -0,0 +1,46 @@ +<% + +use FS::Record qw(qsearch dbdef); +use DBIx::DBSchema; +use DBIx::DBSchema::Table; + +my $error; +my $p2 = popurl(2); +my ($table) = $cgi->keywords; +my $dbdef = dbdef or die "Cannot fetch dbdef!"; +my $dbdef_table = $dbdef->table($table) or die "Cannot fetch schema for $table"; + +my $pkey = $dbdef_table->primary_key or die "Cannot fetch pkey for $table"; +print header("Browse $table", menubar('Main Menu' => $p)); + +my @rec = qsearch($table, {}); +my @col = $dbdef_table->columns; + +if ($cgi->param('error')) { %> + Error: <%=$cgi->param('error')%> +

    +<% } +%> +Add a new <%=$table%>

    + +<%=table()%> + +<% foreach (grep { $_ ne $pkey } @col) { + %><%=$_%> + <% } %> + +<% foreach $rec (sort {$a->getfield($pkey) cmp $b->getfield($pkey) } @rec) { + %> + + + + <%=$rec->getfield($pkey)%> <% + foreach $col (grep { $_ ne $pkey } @col) { %> + <%=$rec->getfield($col)%> <% } %> + + +<% } %> + + + + diff --git a/httemplate/browse/part_sb_field.cgi b/httemplate/browse/part_sb_field.cgi new file mode 100644 index 000000000..4c9641e86 --- /dev/null +++ b/httemplate/browse/part_sb_field.cgi @@ -0,0 +1,31 @@ +<%= header('svc_broadband extended fields', menubar('Main Menu' => $p)) %> +<% + +my @psf = qsearch('part_sb_field', {}); +my $block; +my $p2 = popurl(2); + +%> + +<% if ($cgi->param('error')) { %> + Error: <%=$cgi->param('error')%> +

    +<% } %> + +Add a new field

    + +<%=table()%> +Field nameService type +<% foreach $psf (sort {$a->name cmp $b->name} @psf) { %> + + + + + <%=$psf->name%> + <%=$psf->part_svc->svc%> + +<% } %> + + + + diff --git a/httemplate/browse/router.cgi b/httemplate/browse/router.cgi new file mode 100644 index 000000000..8864936d9 --- /dev/null +++ b/httemplate/browse/router.cgi @@ -0,0 +1,37 @@ +<%= header('Routers', menubar('Main Menu' => $p)) %> +<% + +my @router = qsearch('router', {}); +my $p2 = popurl(2); + +%> + +<% if ($cgi->param('error')) { %> + Error: <%=$cgi->param('error')%> +

    +<% } %> + +Add a new router

    + +<%=table()%> + +<% foreach $router (sort {$a->routernum <=> $b->routernum} @router) { %> + + + + <%=$router->routername%> + + + +<% } %> + + + + diff --git a/httemplate/edit/part_router_field.cgi b/httemplate/edit/part_router_field.cgi new file mode 100644 index 000000000..c3e99be2c --- /dev/null +++ b/httemplate/edit/part_router_field.cgi @@ -0,0 +1,70 @@ + +<% +my ($routerfieldpart, $part_router_field); + +if ( $cgi->param('error') ) { + $part_router_field = new FS::part_router_field ( { + map { $_, scalar($cgi->param($_)) } fields('part_router_field')}); + $routerfieldpart = $part_router_field->routerfieldpart; +} else { + my($query) = $cgi->keywords; + if ( $query =~ /^(\d+)$/ ) { #editing + $routerfieldpart=$1; + $part_router_field=qsearchs('part_router_field', + {'routerfieldpart' => $routerfieldpart}) + or die "Unknown routerfieldpart!"; + + } else { #adding + $part_router_field = new FS::part_router_field({}); + } +} +my $action = $part_router_field->routerfieldpart ? 'Edit' : 'Add'; + +my $p1 = popurl(1); +print header("$action Router Extended Field Definition", ''); + +print qq!Error: !, $cgi->param('error'), + "" + if $cgi->param('error'); +%> +
    + + + + +Field #<%=$routerfieldpart or "(NEW)"%>

    + +<%=ntable("#cccccc",2)%> + + Name + + + + Length + + + + check_block + + + + list_source + + +
    + +
    + +

    +If you don't understand what check_block and +list_source mean, LEAVE THEM BLANK. We mean it. + + + + diff --git a/httemplate/edit/part_sb_field.cgi b/httemplate/edit/part_sb_field.cgi new file mode 100644 index 000000000..9e0cc9e4f --- /dev/null +++ b/httemplate/edit/part_sb_field.cgi @@ -0,0 +1,79 @@ + +<% +my ($sbfieldpart, $part_sb_field); + +if ( $cgi->param('error') ) { + $part_sb_field = new FS::part_sb_field ( { + map { $_, scalar($cgi->param($_)) } fields('part_sb_field')}); + $sbfieldpart = $part_sb_field->sbfieldpart; +} else { + my($query) = $cgi->keywords; + if ( $query =~ /^(\d+)$/ ) { #editing + $sbfieldpart=$1; + $part_sb_field=qsearchs('part_sb_field', + {'sbfieldpart' => $sbfieldpart}) + or die "Unknown sbfieldpart!"; + + } else { #adding + $part_sb_field = new FS::part_sb_field({}); + } +} +my $action = $part_sb_field->sbfieldpart ? 'Edit' : 'Add'; + +my $p1 = popurl(1); +print header("$action svc_broadband Extended Field Definition", ''); + +print qq!Error: !, $cgi->param('error'), + "" + if $cgi->param('error'); +%> +
    + + + + +Field #<%=$sbfieldpart or "(NEW)"%>

    + +<%=ntable("#cccccc",2)%> + + Name + + + + Length + + + + Service + + + check_block + + + + list_source + + +
    + +
    + +

    +If you don't understand what check_block and +list_source mean, LEAVE THEM BLANK. We mean it. + + + + diff --git a/httemplate/edit/process/addr_block/add.cgi b/httemplate/edit/process/addr_block/add.cgi new file mode 100755 index 000000000..34d799ccd --- /dev/null +++ b/httemplate/edit/process/addr_block/add.cgi @@ -0,0 +1,20 @@ +<% + +my $error = ''; +my $ip_gateway = $cgi->param('ip_gateway'); +my $ip_netmask = $cgi->param('ip_netmask'); + +my $new = new FS::addr_block { + ip_gateway => $ip_gateway, + ip_netmask => $ip_netmask, + routernum => 0 }; + +$error = $new->insert; + +if ( $error ) { + $cgi->param('error', $error); + print $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string ); +} else { + print $cgi->redirect(popurl(4). "browse/addr_block.cgi"); +} +%> diff --git a/httemplate/edit/process/addr_block/allocate.cgi b/httemplate/edit/process/addr_block/allocate.cgi new file mode 100755 index 000000000..85b0d7a7a --- /dev/null +++ b/httemplate/edit/process/addr_block/allocate.cgi @@ -0,0 +1,25 @@ +<% +my $error = ''; +my $blocknum = $cgi->param('blocknum'); +my $routernum = $cgi->param('routernum'); + +my $addr_block = qsearchs('addr_block', { blocknum => $blocknum }); +my $router = qsearchs('router', { routernum => $routernum }); + +if($addr_block) { + if ($router) { + $error = $addr_block->allocate($router); + } else { + $error = "Cannot find router with routernum $routernum"; + } +} else { + $error = "Cannot find block with blocknum $blocknum"; +} + +if ( $error ) { + $cgi->param('error', $error); + print $cgi->redirect(popurl(4). "browse/addr_block.cgi?" . $cgi->query_string); +} else { + print $cgi->redirect(popurl(4). "browse/addr_block.cgi"); +} +%> diff --git a/httemplate/edit/process/addr_block/deallocate.cgi b/httemplate/edit/process/addr_block/deallocate.cgi new file mode 100755 index 000000000..cfb7ed04d --- /dev/null +++ b/httemplate/edit/process/addr_block/deallocate.cgi @@ -0,0 +1,24 @@ +<% +my $error = ''; +my $blocknum = $cgi->param('blocknum'); + +my $addr_block = qsearchs('addr_block', { blocknum => $blocknum }); + +if($addr_block) { + my $router = $addr_block->router; + if ($router) { + $error = $addr_block->deallocate($router); + } else { + $error = "Block is not allocated to a router"; + } +} else { + $error = "Cannot find block with blocknum $blocknum"; +} + +if ( $error ) { + $cgi->param('error', $error); + print $cgi->redirect(popurl(4). "browse/addr_block.cgi?" . $cgi->query_string); +} else { + print $cgi->redirect(popurl(4). "browse/addr_block.cgi"); +} +%> diff --git a/httemplate/edit/process/addr_block/split.cgi b/httemplate/edit/process/addr_block/split.cgi new file mode 100755 index 000000000..bb6d4ba3e --- /dev/null +++ b/httemplate/edit/process/addr_block/split.cgi @@ -0,0 +1,19 @@ +<% +my $error = ''; +my $blocknum = $cgi->param('blocknum'); +my $addr_block = qsearchs('addr_block', { blocknum => $blocknum }); + +if ( $addr_block) { + $error = $addr_block->split_block; +} else { + $error = "Unknown blocknum: $blocknum"; +} + + +if ( $error ) { + $cgi->param('error', $error); + print $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string ); +} else { + print $cgi->redirect(popurl(4). "browse/addr_block.cgi"); +} +%> diff --git a/httemplate/edit/process/generic.cgi b/httemplate/edit/process/generic.cgi new file mode 100644 index 000000000..751987f7a --- /dev/null +++ b/httemplate/edit/process/generic.cgi @@ -0,0 +1,69 @@ +<% + +# Welcome to generic.cgi. +# +# This script provides a generic edit/process/ backend for simple table +# editing. All it knows how to do is take the values entered into +# the script and insert them into the table specified by $cgi->param('table'). +# If there's an existing record with the same primary key, it will be +# replaced. (Deletion will be added in the future.) +# +# Special cgi params for this script: +# table: the name of the table to be edited. The script will die horribly +# if it can't find the table. +# redirect_ok: URL to be displayed after a successful edit. The value of +# the record's primary key will be passed as a keyword. +# Defaults to (freeside root)/view/$table.cgi. +# redirect_error: URL to be displayed if there's an error. The original +# query string, plus the error message, will be passed. +# Defaults to $cgi->referer() (i.e. go back where you +# came from). + + +use FS::Record qw(qsearchs dbdef); +use DBIx::DBSchema; +use DBIx::DBSchema::Table; + + +my $error; +my $p2 = popurl(2); +my $table = $cgi->param('table'); +my $dbdef = dbdef or die "Cannot fetch dbdef!"; + +my $dbdef_table = $dbdef->table($table) or die "Cannot fetch schema for $table"; + +my $pkey = $dbdef_table->primary_key or die "Cannot fetch pkey for $table"; +my $pkey_val = $cgi->param($pkey); + + +#warn "new FS::Record ( $table, (hashref) )"; +my $new = FS::Record::new ( "FS::$table", { + map { $_, scalar($cgi->param($_)) } fields($table) +} ); + +#warn 'created $new of class '.ref($new); + +if($pkey_val and (my $old = qsearchs($table, { $pkey, $pkey_val} ))) { + # edit + $error = $new->replace($old); +} else { + #add + $error = $new->insert; + $pkey_val = $new->getfield($pkey); + # New records usually don't have their primary keys set until after + # they've been checked/inserted, so grab the new $pkey_val so we can + # redirect to it. +} + +my $redirect_ok = (($cgi->param('redirect_ok')) ? + $cgi->param('redirect_ok') : $p2."view/$table.cgi"); +my $redirect_error = (($cgi->param('redirect_error')) ? + $cgi->param('redirect_error') : $cgi->referer()); + +if($error) { + $cgi->param('error', $error); + print $cgi->redirect($redirect_error . '?' . $cgi->query_string); +} else { + print $cgi->redirect($redirect_ok . '?' .$pkey_val); +} +%> diff --git a/httemplate/edit/process/router.cgi b/httemplate/edit/process/router.cgi new file mode 100644 index 000000000..c0cb884f0 --- /dev/null +++ b/httemplate/edit/process/router.cgi @@ -0,0 +1,100 @@ +<% + +use FS::UID qw(dbh); + +my $dbh = dbh; +local $FS::UID::AutoCommit=0; + +sub check { + my $error = shift; + if($error) { + $cgi->param('error', $error); + print $cgi->redirect(popurl(3) . "edit/router.cgi?". $cgi->query_string); + $dbh->rollback; + exit; + } +} + +my $error = ''; +my $routernum = $cgi->param('routernum'); +my $routername = $cgi->param('routername'); +my $old = qsearchs('router', { routernum => $routernum }); +my @old_rf; +my @old_psr; + +my $new = new FS::router { + routernum => $routernum, + routername => $routername, + svcnum => 0 + }; + +if($old) { + if($old->routername ne $new->routername) { + $error = $new->replace($old); + } #else do nothing +} else { + $error = $new->insert; +} + +check($error); + +if ($old) { + @old_psr = $old->part_svc_router; + foreach $psr (@old_psr) { + if($cgi->param('svcpart_'.$psr->svcpart) eq 'ON') { + # do nothing + } else { + $error = $psr->delete; + } + } + check($error); + @old_rf = $old->router_field; + foreach $rf (@old_rf) { + if(my $new_val = $cgi->param('rf_'.$rf->routerfieldpart)) { + if($new_val ne $rf->value) { + my $new_rf = new FS::router_field + { routernum => $routernum, + value => $new_val, + routerfieldpart => $rf->routerfieldpart }; + $error = $new_rf->replace($rf); + } #else do nothing + } else { + $error = $rf->delete; + } + check($error); + } +} + +foreach($cgi->param) { + if($cgi->param($_) eq 'ON' and /^svcpart_(\d+)$/) { + my $svcpart = $1; + if(grep {$_->svcpart == $svcpart} @old_psr) { + # do nothing + } else { + my $new_psr = new FS::part_svc_router { svcpart => $svcpart, + routernum => $routernum }; + $error = $new_psr->insert; + } + check($error); + } elsif($cgi->param($_) ne '' and /^rf_(\d+)$/) { + my $part = $1; + if(my @x = grep {$_->routerfieldpart == $part} @old_rf) { + # already handled all of these + } else { + my $new_rf = new FS::router_field + { routernum => $routernum, + value => $cgi->param('rf_'.$part), + routerfieldpart => $part }; + $error = $new_rf->insert; + check($error); + } + } +} + + + +# Yay, everything worked! +$dbh->commit or die $dbh->errstr; +print $cgi->redirect(popurl(3). "edit/router.cgi?$routernum"); + +%> diff --git a/httemplate/edit/process/svc_broadband.cgi b/httemplate/edit/process/svc_broadband.cgi index fd7ba20d5..ab8b9f9d8 100644 --- a/httemplate/edit/process/svc_broadband.cgi +++ b/httemplate/edit/process/svc_broadband.cgi @@ -1,12 +1,19 @@ <% +# If it's stupid but it works, it's not stupid. +# -- U.S. Army + +local $FS::UID::AutoCommit = 0; +my $dbh = FS::UID::dbh; + $cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; my $svcnum = $1; -my $old; +my $old; my @old_sbf; if ( $svcnum ) { $old = qsearchs('svc_broadband', { 'svcnum' => $svcnum } ) or die "fatal: can't find broadband service (svcnum $svcnum)!"; + @old_sbf = $old->sb_field; } else { $old = ''; } @@ -17,14 +24,6 @@ my $new = new FS::svc_broadband ( { } ( 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); @@ -33,12 +32,47 @@ if ( $svcnum ) { $svcnum = $new->svcnum; } +unless ($error) { + my $sb_field; + + foreach ($cgi->param) { + #warn "\$cgi->param $_: " . $cgi->param($_); + if(/^sbf_(\d+)/) { + my $part = $1; + #warn "\$part $part"; + $sb_field = new FS::sb_field + { svcnum => $svcnum, + value => $cgi->param($_), + sbfieldpart => $part }; + if (my @x = grep { $_->sbfieldpart eq $part } @old_sbf) { + #if (my $old_sb_field = (grep { $_->sbfieldpart eq $part} @old_Sbf)[0]) { + #warn "array: " . scalar(@x); + if (length($sb_field->value) && ($sb_field->value ne $x[0]->value)) { + #warn "replacing " . $x[0]->value . " with " . $sb_field->value; + $error = $sb_field->replace($x[0]); + #$error = $sb_field->replace($old_sb_field); + } elsif (length($sb_field->value) == 0) { + #warn "delete"; + $error = $x[0]->delete; + } + } else { + if (length($sb_field->value) > 0) { + #warn "insert"; + $error = $sb_field->insert; + } + # else do nothing + } + } + } +} + if ( $error ) { $cgi->param('error', $error); $cgi->param('ip_addr', $new->ip_addr); - $cgi->param('mac_addr', $new->mac_addr); + $dbh->rollback; print $cgi->redirect(popurl(2). "svc_broadband.cgi?". $cgi->query_string ); } else { + $dbh->commit or die $dbh->errstr; print $cgi->redirect(popurl(3). "view/svc_broadband.cgi?" . $svcnum ); } diff --git a/httemplate/edit/router.cgi b/httemplate/edit/router.cgi new file mode 100755 index 000000000..d2279ff00 --- /dev/null +++ b/httemplate/edit/router.cgi @@ -0,0 +1,88 @@ + + +<% + +my $router; +if ( $cgi->keywords ) { + my($query) = $cgi->keywords; + $query =~ /^(\d+)$/; + $router = qsearchs('router', { routernum => $1 }) + or print $cgi->redirect(popurl(2)."browse/router.cgi") ; +} else { + $router = new FS::router ( { + map { $_, scalar($cgi->param($_)) } fields('router') + } ); +} + +my $routernum = $router->routernum; +my $action = $routernum ? 'Edit' : 'Add'; +my $hashref = $router->hashref; + +print header("$action Router", menubar( + 'Main Menu' => "$p", + 'View all routers' => "${p}browse/router.cgi", +)); + +if($cgi->param('error')) { +%> Error: <%=$cgi->param('error')%> +<% } %> + +
    + + Router #<%=$routernum or "(NEW)"%> + +

    Name +<%=table() %> + +<% +# I know, I know. Massive false laziness with edit/svc_broadband.cgi. But +# Kristian won't let me generalize the custom field mechanism to every table in +# the database, so this is what we get. +# -- MW + +my @part_router_field = qsearch('part_router_field', { }); +my %rf = map { $_->part_router_field->name, $_->value } $router->router_field; +foreach (sort { $a->name cmp $b->name } @part_router_field) { + %> + + <%=$_->name%> + <% + if(my @opts = $_->list_values) { + %> + <% } else { %> + length ? 'SIZE="'.$_->length.'"' : ''%>> + <% } %> + +<% } %> + + + + +

    Select the service types available on this router
    +<% + +foreach my $part_svc ( qsearch('part_svc', { svcdb => 'svc_broadband', + disabled => '' }) ) { + %> +
    + $part_svc->svcpart, + routernum => $routernum } ) ? 'CHECKED' : ''%> VALUE="ON"> + + <%=$part_svc->svcpart%>: <%=$part_svc->svc%> + <% } %> + +

    + + + diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi index d8a1f7a2a..f017d7a6e 100644 --- a/httemplate/edit/svc_broadband.cgi +++ b/httemplate/edit/svc_broadband.cgi @@ -1,6 +1,13 @@ <% +# If it's stupid but it works, it's still stupid. +# -Kristian + + +use HTML::Widgets::SelectLayers; +use Tie::IxHash; + my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_broadband ); if ( $cgi->param('error') ) { $svc_broadband = new FS::svc_broadband ( { @@ -38,6 +45,8 @@ if ( $cgi->param('error') ) { $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); die "No part_svc entry!" unless $part_svc; + $svc_broadband->setfield('svcpart', $svcpart); + $svcnum=''; #set fixed and default fields from part_svc @@ -53,12 +62,9 @@ if ( $cgi->param('error') ) { } 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') }); + #Nothing? } elsif ( $action eq 'Edit' ) { @@ -68,152 +74,111 @@ if ($pkgnum) { die "\$action eq Add, but \$pkgnum is null!\n"; } - my $p1 = popurl(1); -print header("Broadband Service $action", ''); - -print qq!Error: !, $cgi->param('error'), - "" - if $cgi->param('error'); - -print qq!
    !; - -#display - - - -#svcnum -print qq!!; -print qq!Service #!, $svcnum ? $svcnum : "(NEW)", "

    "; - -#pkgnum -print qq!!; - -#svcpart -print qq!!; - -#actypenum -print ''; - - -print &ntable("#cccccc",2) . qq!AC!; - -#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!! . - qq!${acnum}: ${acname}!; - -} else { - - my @ac_list = qsearch('ac', { actypenum => $svc_broadband->actypenum }); - print qq!! . - qq!${speed_down}Kbps!; -} else { - print qq!! . - qq!Kbps!; -} - -print 'Upload speed'; -if ( $part_svc->part_svc_column('speed_up')->columnflag eq 'F' ) { - print qq!! . - qq!${speed_up}Kbps!; -} else { - print qq!! . - qq!Kbps!; -} +my ($ip_addr, $speed_up, $speed_down, $blocknum) = + ($svc_broadband->ip_addr, + $svc_broadband->speed_up, + $svc_broadband->speed_down, + $svc_broadband->blocknum); -#ip_addr & ip_netmask -#We're assuming that ip_netmask is fixed if ip_addr is fixed. -#If it isn't, well, what the heck are you doing!?!? - -my ($ip_addr, $ip_netmask) = ($svc_broadband->ip_addr, - $svc_broadband->ip_netmask); - -print 'IP address/Mask'; -if ( $part_svc->part_svc_column('ip_addr')->columnflag eq 'F' ) { - print qq!! . - qq!! . - qq!${ip_addr}/${ip_netmask}!; -} else { - $ip_addr =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; - print <. - . - . - / - - - -

    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.

    - -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 'MAC Address'; - if ( $part_svc->part_svc_column('mac_addr')->columnflag eq 'F' ) { #Why? - print qq!! . - qq!${mac_addr}!; - } 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 +%> +<%=header("Broadband Service $action", '')%> + +<% if ($cgi->param('error')) { %> +Error: <%=$cgi->param('error')%>
    +<% } %> + +Service #<%=$svcnum ? $svcnum : "(NEW)"%>

    + + + + + + + + <%=&ntable("#cccccc",2)%> + + + Download speed + +<% if ( $part_svc->part_svc_column('speed_down')->columnflag eq 'F' ) { %> + <%=$speed_down%>Kbps +<% } else { %> + Kbps +<% } %> + + + + Upload speed + +<% if ( $part_svc->part_svc_column('speed_up')->columnflag eq 'F' ) { %> + <%=$speed_up%>Kbps +<% } else { %> + Kbps +<% } %> + + +<% if ($action eq 'Add') { %> + + Router/Block + + + + +<% } else { %> -print 'Location'; -if ( $part_svc->part_svc_column('location')->columnflag eq 'F' ) { - print qq!
    ${location}
    !; -} else { - print qq!!; -} + + Router/Block + + <%=$svc_broadband->addr_block->router->routername%>:<%=$svc_broadband->addr_block->NetAddr%> + + + -print '
    '; +<% } %> -print < - + my @part_sb_field = qsearch('part_sb_field', { svcpart => $svcpart }); + my $sbf_hashref = $svc_broadband->sb_field_hashref($svcpart); + foreach (sort { $a->name cmp $b->name } @part_sb_field) { + %> + + <%=$_->name%> + <% + if(my @opts = $_->list_values) { + %> + + <% } else { %> + length ? 'SIZE="'.$_->length.'"' : ''%>> + <% } %> + + +<% } %> + +
    + + + -END -%> + diff --git a/httemplate/index.html b/httemplate/index.html index e8c3681b4..d13649b07 100644 --- a/httemplate/index.html +++ b/httemplate/index.html @@ -194,12 +194,16 @@ into counties and assign different tax rates to each.
  • View/Edit Access Numbers - Points of Presence -
  • View/Edit AC Types - - Broadband service access concentrator types. -
  • View/Edit AC - - Broadband service access concentrators.
  • View/Edit invoice events - Actions for overdue invoices -
  • View/Edit message catalog - Change error messages and other customizable labels. +
  • View/Edit message catalog - Change error messages and other customizable labels. +
  • View/Edit custom svc_broadband fields + - Custom broadband service fields for site-specific export/informational data. +
  • View/Edit custom router fields + - Custom router fields for site-specific export data. +
  • View/Edit routers + - Broadband access routers +
  • View/Edit address blocks + - Manage address blocks and block assignments to broadband routers.
    diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi index 156edfaec..164b5b22c 100644 --- a/httemplate/view/svc_broadband.cgi +++ b/httemplate/view/svc_broadband.cgi @@ -20,28 +20,26 @@ if ($pkgnum) { } #eofalse -my $ac = qsearchs('ac', { acnum => $svc_broadband->getfield('acnum') }); +my $router = $svc_broadband->addr_block->router; + +if (not $router) { die "Could not lookup router for svc_broadband (svcnum $svcnum)" }; my ( - $acname, - $acnum, + $routername, + $routernum, $speed_down, $speed_up, - $ip_addr, - $ip_netmask, - $mac_addr, - $location + $ip_addr ) = ( - $ac->getfield('acname'), - $ac->getfield('acnum'), + $router->getfield('routername'), + $router->getfield('routernum'), $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') + $svc_broadband->getfield('ip_addr') ); + + print header('Broadband Service View', menubar( ( ( $custnum ) ? ( "View this package (#$pkgnum)" => "${p}view/cust_pkg.cgi?$pkgnum", @@ -56,20 +54,38 @@ print header('Broadband Service View', menubar( ntable("#cccccc"). ''. ntable("#cccccc",2). qq!Service number!. qq!$svcnum!. - qq!AC!. - qq!$acnum: $acname!. + qq!Router!. + qq!$routernum: $routername!. qq!Download Speed!. qq!$speed_down!. qq!Upload Speed!. qq!$speed_up!. - qq!IP Address/Mask!. - qq!$ip_addr/$ip_netmask!. - qq!MAC Address!. - qq!$mac_addr!. - qq!Location!. - qq!
    $location
    !. - ''. - '
    '. joblisting({'svcnum'=>$svcnum}, 1). + qq!IP Address!. + qq!$ip_addr!. + ''; + + +# foreach my $sb_field +# ( qsearch('sb_field', { svcnum => $svcnum }) ) { +# my $part_sb_field = qsearchs('part_sb_field', +# { sbfieldpart => $sb_field->sbfieldpart }); +# print q!! . $part_sb_field->name . +# q!! . $sb_field->value . +# q!!; +# } +# print ''; + + + my $sb_field = $svc_broadband->sb_field_hashref; + foreach (sort { $a cmp $b } keys(%{$sb_field})) { + print q!! . $_ . + q!! . $sb_field->{$_} . + q!!; + } + print ''; + + +print '
    '. joblisting({'svcnum'=>$svcnum}, 1). '' ; %> -- cgit v1.2.1 From 6e2dcb26245ef419438f60e99c91873a8d762625 Mon Sep 17 00:00:00 2001 From: khoff Date: Wed, 5 Feb 2003 23:23:00 +0000 Subject: svc_broadband rewrite --- FS/FS/ac.pm | 148 --------------------------- FS/FS/ac_block.pm | 148 --------------------------- FS/FS/ac_field.pm | 138 ------------------------- FS/FS/ac_type.pm | 128 ----------------------- FS/FS/part_ac_field.pm | 102 ------------------- httemplate/browse/ac.cgi | 57 ----------- httemplate/browse/ac_type.cgi | 47 --------- httemplate/edit/ac.cgi | 163 ------------------------------ httemplate/edit/ac_type.cgi | 106 ------------------- httemplate/edit/process/ac.cgi | 28 ----- httemplate/edit/process/ac_block.cgi | 21 ---- httemplate/edit/process/ac_field.cgi | 21 ---- httemplate/edit/process/ac_type.cgi | 28 ----- httemplate/edit/process/part_ac_field.cgi | 21 ---- 14 files changed, 1156 deletions(-) delete mode 100644 FS/FS/ac.pm delete mode 100755 FS/FS/ac_block.pm delete mode 100755 FS/FS/ac_field.pm delete mode 100755 FS/FS/ac_type.pm delete mode 100755 FS/FS/part_ac_field.pm delete mode 100755 httemplate/browse/ac.cgi delete mode 100755 httemplate/browse/ac_type.cgi delete mode 100755 httemplate/edit/ac.cgi delete mode 100755 httemplate/edit/ac_type.cgi delete mode 100755 httemplate/edit/process/ac.cgi delete mode 100755 httemplate/edit/process/ac_block.cgi delete mode 100755 httemplate/edit/process/ac_field.cgi delete mode 100755 httemplate/edit/process/ac_type.cgi delete mode 100755 httemplate/edit/process/part_ac_field.cgi diff --git a/FS/FS/ac.pm b/FS/FS/ac.pm deleted file mode 100644 index 5a2b36079..000000000 --- a/FS/FS/ac.pm +++ /dev/null @@ -1,148 +0,0 @@ -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 - -=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 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 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 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, L, L, L, schema.html -from the base documentation. - -=cut - -1; - diff --git a/FS/FS/ac_block.pm b/FS/FS/ac_block.pm deleted file mode 100755 index 09de6a4d8..000000000 --- a/FS/FS/ac_block.pm +++ /dev/null @@ -1,148 +0,0 @@ -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) 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 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 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 deleted file mode 100755 index f6011192f..000000000 --- a/FS/FS/ac_field.pm +++ /dev/null @@ -1,138 +0,0 @@ -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 contains values of fields defined by L -for an L. Values must be of the data type defined by ut_type in -L. -Supported fields as follows: - -=over 4 - -=item acfieldpart - Type of ac_field as defined by L - -=item acnum - The L 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 that defines this L - -=cut - -sub part_ac_field { - my $self = shift; - - return qsearchs('part_ac_field', { acfieldpart => $self->acfieldpart }); -} - -=item ac - -Returns a reference to the L to which this L belongs. - -=cut - -sub ac { - my $self = shift; - - return qsearchs('ac', { acnum => $self->acnum }); -} - -=back - -=head1 VERSION - -$Id: - -=head1 BUGS - -=head1 SEE ALSO - -L, L, L, L, schema.html -from the base documentation. - -=cut - -1; - diff --git a/FS/FS/ac_type.pm b/FS/FS/ac_type.pm deleted file mode 100755 index e83c5c5f0..000000000 --- a/FS/FS/ac_type.pm +++ /dev/null @@ -1,128 +0,0 @@ -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 refers to a type of access concentrator. L -records refer to a specific L 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 - -=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 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 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, L, L, L, schema.html -from the base documentation. - -=cut - -1; - diff --git a/FS/FS/part_ac_field.pm b/FS/FS/part_ac_field.pm deleted file mode 100755 index dcb445253..000000000 --- a/FS/FS/part_ac_field.pm +++ /dev/null @@ -1,102 +0,0 @@ -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, L, L, L, schema.html -from the base documentation. - -=cut - -1; - diff --git a/httemplate/browse/ac.cgi b/httemplate/browse/ac.cgi deleted file mode 100755 index 0ae138d3b..000000000 --- a/httemplate/browse/ac.cgi +++ /dev/null @@ -1,57 +0,0 @@ - -<%= header('Access Concentrator Listing', menubar( - 'Main Menu' => $p, - 'Access Concentrator Types' => $p. 'browse/ac_type.cgi', -)) %> -
    -Add a new Access Concentrator

    - -<%= table() %> - - AC - AC Type - Fields - Network/Mask - -<% - -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 < - - $hashref->{acnum} - - $hashref->{acname} - $actypename - -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') . '
    '; - } - print ''; - - 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 . '
    '; - } - - print "\n"; - -} - -print < - - -END - -%> diff --git a/httemplate/browse/ac_type.cgi b/httemplate/browse/ac_type.cgi deleted file mode 100755 index 0ad8271d3..000000000 --- a/httemplate/browse/ac_type.cgi +++ /dev/null @@ -1,47 +0,0 @@ - -<% - -print header('Access Concentrator Types', menubar( - 'Main Menu' => $p, - 'Access Concentrators' => $p. 'browse/ac.cgi', -)) %> -
    -Add new AC Type

    -<%= table() %> - - - Type - Fields - - -<% -foreach my $ac_type ( qsearch('ac_type',{}) ) { - my($hashref)=$ac_type->hashref; - print < - - $hashref->{actypenum} - - $hashref->{actypename} - -END - - foreach ( qsearch('part_ac_field', { actypenum => $hashref->{actypenum} }) ) { - my ($part_ac_field) = $_->hashref; - print $part_ac_field->{'name'} . - ' (' . $part_ac_field->{'ut_type'} . ')
    '; - } - -} - -print < - - - - - - -END - -%> diff --git a/httemplate/edit/ac.cgi b/httemplate/edit/ac.cgi deleted file mode 100755 index 86b05a4a1..000000000 --- a/httemplate/edit/ac.cgi +++ /dev/null @@ -1,163 +0,0 @@ - -<% - -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!Error: !, $cgi->param('error'), - "" - if $cgi->param('error'); - -print '
    ', - qq!!, - "Access Concentrator #", $hashref->{acnum} ? $hashref->{acnum} : "(NEW)"; - -print < - - Access Concentrator - - - - -END - - -if (! $ac->acnum) { - print < - Access Concentrator Type - !; -} - -print qq!

    !; - -if ($hashref->{acnum}) { - print table(); - print < - - Field Name - Field Value - -END - - #my @ac_fields = qsearch('ac_field', { acnum => $hashref->{acnum} }); - my @ac_fields = $ac->ac_field; - foreach (@ac_fields) { - print qq!\n!; - my $part_ac_field = qsearchs('part_ac_field', - { acfieldpart => $_->getfield('acfieldpart') }); - print '' . $part_ac_field->getfield('name') . - '' . $_->getfield('value') . ''; - print "\n"; - } - - print '
    '; - print < - - - - (NEW) - - - - - - -END - -} - -if ($hashref->{acnum}) { - - print qq!

    IP Address Blocks:
    ! . table() . - qq!Network/Mask! . - qq!Gateway AddressMask length\n!; - - foreach (qsearch('ac_block', { acnum => $hashref->{acnum} })) { - my $ip_addr = new NetAddr::IP($_->getfield('ip_gateway'), - $_->getfield('ip_netmask')); - print qq!! . $ip_addr->network->addr() . '/' . - $ip_addr->network->mask() . qq!!; - - print qq!! . $_->getfield('ip_gateway') . qq!\n! . - qq!! . $_->getfield('ip_netmask') . qq!!; - - } - - print '
    '; - print < - - - - (NEW) - - - - - - - - -END - -} - -print < - -END - -%> diff --git a/httemplate/edit/ac_type.cgi b/httemplate/edit/ac_type.cgi deleted file mode 100755 index ccc3d579c..000000000 --- a/httemplate/edit/ac_type.cgi +++ /dev/null @@ -1,106 +0,0 @@ - -<% - -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!Error: !, $cgi->param('error'), - "" - if $cgi->param('error'); - -print qq!
    !; - -#display - -print qq!!, - "AC Type #", $hashref->{actypenum} ? $hashref->{actypenum} : "(NEW)"; - -print < -AC Type Name - - -TROZ - -print qq!
    !; - - -if ($hashref->{actypenum}) { - print qq!
    Available fields:
    ! . table(); - - print qq! Field nameField type!; - - my @part_ac_field = qsearch ( 'part_ac_field', - { actypenum => $hashref->{actypenum} } ); - foreach ( @part_ac_field ) { - my $pf_hashref = $_->hashref; - print < - $pf_hashref->{acfieldpart} - $pf_hashref->{name} - $pf_hashref->{ut_type} - -END - } - - my $name, $ut_type = ''; - if ($cgi->param('error')) { - $name = $cgi->param('name'); - $ut_type = $cgi->param('ut_type'); - } - - print < - - (NEW) - - - - - - - - - - -END - -} - -%> - - - - diff --git a/httemplate/edit/process/ac.cgi b/httemplate/edit/process/ac.cgi deleted file mode 100755 index fc434a807..000000000 --- a/httemplate/edit/process/ac.cgi +++ /dev/null @@ -1,28 +0,0 @@ -<% - -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 deleted file mode 100755 index b1c3c726b..000000000 --- a/httemplate/edit/process/ac_block.cgi +++ /dev/null @@ -1,21 +0,0 @@ -<% - -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 deleted file mode 100755 index 2bfe3312f..000000000 --- a/httemplate/edit/process/ac_field.cgi +++ /dev/null @@ -1,21 +0,0 @@ -<% - -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 deleted file mode 100755 index ca232ba58..000000000 --- a/httemplate/edit/process/ac_type.cgi +++ /dev/null @@ -1,28 +0,0 @@ -<% - -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 deleted file mode 100755 index 38ad586f7..000000000 --- a/httemplate/edit/process/part_ac_field.cgi +++ /dev/null @@ -1,21 +0,0 @@ -<% - -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')); -} - -%> -- cgit v1.2.1 From c232fac0743999105f6948b9fa352fe2293b09f8 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 6 Feb 2003 05:26:50 +0000 Subject: time/data detail on invoices --- FS/FS.pm | 2 + FS/FS/cust_bill.pm | 41 +++++++------- FS/FS/cust_bill_pkg.pm | 67 ++++++++++++++++++++-- FS/FS/cust_bill_pkg_detail.pm | 123 +++++++++++++++++++++++++++++++++++++++++ FS/FS/cust_main.pm | 13 +++-- FS/MANIFEST | 2 + FS/bin/freeside-setup | 12 ++++ FS/t/cust_bill_pkg_detail.t | 5 ++ httemplate/docs/schema.html | 7 +++ httemplate/docs/upgrade10.html | 11 ++++ httemplate/edit/part_pkg.cgi | 6 +- 11 files changed, 256 insertions(+), 33 deletions(-) create mode 100644 FS/FS/cust_bill_pkg_detail.pm create mode 100644 FS/t/cust_bill_pkg_detail.t create mode 100644 httemplate/docs/upgrade10.html diff --git a/FS/FS.pm b/FS/FS.pm index a2df6f175..e4a32082c 100644 --- a/FS/FS.pm +++ b/FS/FS.pm @@ -102,6 +102,8 @@ L - Invoice class L - Invoice line item class +L - Invoice line item detail class + L - Invoice event definition class L - Completed invoice event class diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index c661baa95..aa82eb6f8 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -953,38 +953,43 @@ sub print_text { } #new charges - foreach ( ( grep { $_->pkgnum } $self->cust_bill_pkg ), #packages first - ( grep { ! $_->pkgnum } $self->cust_bill_pkg ), #then taxes + foreach my $cust_bill_pkg ( + ( grep { $_->pkgnum } $self->cust_bill_pkg ), #packages first + ( grep { ! $_->pkgnum } $self->cust_bill_pkg ), #then taxes ) { - if ( $_->pkgnum ) { + if ( $cust_bill_pkg->pkgnum ) { - my($cust_pkg)=qsearchs('cust_pkg', { 'pkgnum', $_->pkgnum } ); - my($part_pkg)=qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->pkgpart}); - my($pkg)=$part_pkg->pkg; + my $cust_pkg = qsearchs('cust_pkg', { pkgnum =>$cust_bill_pkg->pkgnum } ); + my $part_pkg = qsearchs('part_pkg', { pkgpart=>$cust_pkg->pkgpart } ); + my $pkg = $part_pkg->pkg; - if ( $_->setup != 0 ) { - push @buf, [ "$pkg Setup", $money_char. sprintf("%10.2f",$_->setup) ]; + if ( $cust_bill_pkg->setup != 0 ) { + push @buf, [ "$pkg Setup", + $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ]; push @buf, map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels; } - if ( $_->recur != 0 ) { + if ( $cust_bill_pkg->recur != 0 ) { push @buf, [ - "$pkg (" . time2str("%x",$_->sdate) . " - " . - time2str("%x",$_->edate) . ")", - $money_char. sprintf("%10.2f",$_->recur) + "$pkg (" . time2str("%x", $cust_bill_pkg->sdate) . " - " . + time2str("%x", $cust_bill_pkg->edate) . ")", + $money_char. sprintf("%10.2f", $cust_bill_pkg->recur) ]; push @buf, map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels; } + push @buf, map { [ " $_", '' ] } $cust_bill_pkg->details; + } else { #pkgnum tax - my $itemdesc = defined $_->dbdef_table->column('itemdesc') - ? ( $_->itemdesc || 'Tax' ) + my $itemdesc = defined $cust_bill_pkg->dbdef_table->column('itemdesc') + ? ( $cust_bill_pkg->itemdesc || 'Tax' ) : 'Tax'; - push @buf,[$itemdesc, $money_char. sprintf("%10.2f",$_->setup) ] - if $_->setup != 0; + push @buf, [ $itemdesc, + $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ] + if $cust_bill_pkg->setup != 0; } } @@ -1128,10 +1133,6 @@ sub print_text { =back -=head1 VERSION - -$Id: cust_bill.pm,v 1.61 2003-01-10 07:41:05 ivan Exp $ - =head1 BUGS The delete method. diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm index 5a1dcd2aa..a6615d05d 100644 --- a/FS/FS/cust_bill_pkg.pm +++ b/FS/FS/cust_bill_pkg.pm @@ -2,11 +2,12 @@ package FS::cust_bill_pkg; use strict; use vars qw( @ISA ); -use FS::Record qw( qsearchs ); +use FS::Record qw( qsearch qsearchs dbdef dbh ); use FS::cust_pkg; use FS::cust_bill; +use FS::cust_bill_pkg_detail; -@ISA = qw(FS::Record ); +@ISA = qw( FS::Record ); =head1 NAME @@ -73,6 +74,51 @@ sub table { 'cust_bill_pkg'; } Adds this line item to the database. If there is an error, returns the error, otherwise returns false. +=cut + +sub insert { + my $self = shift; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $error = $self->SUPER::insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + unless ( defined dbdef->table('cust_bill_pkg_detail') && $self->get('details') ) { + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + return ''; + } + + foreach my $detail ( @{$self->get('details')} ) { + my $cust_bill_pkg_detail = new FS::cust_bill_pkg_detail { + 'pkgnum' => $self->pkgnum, + 'invnum' => $self->invnum, + 'detail' => $detail, + }; + $error = $cust_bill_pkg_detail->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + +} + =item delete Currently unimplemented. I don't remove line items because there would then be @@ -139,11 +185,22 @@ sub cust_pkg { qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } ); } -=back +=item details + +Returns an array of detail information for the invoice line item. -=head1 VERSION +=cut -$Id: cust_bill_pkg.pm,v 1.4 2002-09-21 11:17:39 ivan Exp $ +sub details { + my $self = shift; + return () unless defined dbdef->table('cust_bill_pkg_detail'); + map { $_->detail } + qsearch ( 'cust_bill_pkg_detail', { 'pkgnum' => $self->pkgnum, + 'invnum' => $self->invnum, } ); + #qsearch ( 'cust_bill_pkg_detail', { 'lineitemnum' => $self->lineitemnum }); +} + +=back =head1 BUGS diff --git a/FS/FS/cust_bill_pkg_detail.pm b/FS/FS/cust_bill_pkg_detail.pm new file mode 100644 index 000000000..199de439b --- /dev/null +++ b/FS/FS/cust_bill_pkg_detail.pm @@ -0,0 +1,123 @@ +package FS::cust_bill_pkg_detail; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::cust_bill_pkg_detail - Object methods for cust_bill_pkg_detail records + +=head1 SYNOPSIS + + use FS::cust_bill_pkg_detail; + + $record = new FS::cust_bill_pkg_detail \%hash; + $record = new FS::cust_bill_pkg_detail { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cust_bill_pkg_detail object represents additional detail information for +an invoice line item (see L). FS::cust_bill_pkg_detail +inherits from FS::Record. The following fields are currently supported: + +=over 4 + +=item detailnum - primary key + +=item pkgnum - + +=item invnum - + +=item detail - detail description + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new line item detail. To add the line item detail 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 method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'cust_bill_pkg_detail'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid line item detail. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + $self->ut_numbern('detailnum') + || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum') + || $self->ut_foreign_key('invnum', 'cust_pkg', 'invnum') + || $self->ut_text('detail') + ; + +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L, L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 91ffa451a..807fadbcc 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -947,6 +947,8 @@ sub bill { my %hash = $cust_pkg->hash; my $old_cust_pkg = new FS::cust_pkg \%hash; + my @details = (); + # bill setup my $setup = 0; unless ( $cust_pkg->setup ) { @@ -1040,11 +1042,12 @@ sub bill { } if ( $setup > 0 || $recur > 0 ) { my $cust_bill_pkg = new FS::cust_bill_pkg ({ - 'pkgnum' => $cust_pkg->pkgnum, - 'setup' => $setup, - 'recur' => $recur, - 'sdate' => $sdate, - 'edate' => $cust_pkg->bill, + 'pkgnum' => $cust_pkg->pkgnum, + 'setup' => $setup, + 'recur' => $recur, + 'sdate' => $sdate, + 'edate' => $cust_pkg->bill, + 'details' => \@details, }); push @cust_bill_pkg, $cust_bill_pkg; $total_setup += $setup; diff --git a/FS/MANIFEST b/FS/MANIFEST index b3de623d7..e8b1da7a7 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -51,6 +51,7 @@ FS/agent.pm FS/agent_type.pm FS/cust_bill.pm FS/cust_bill_pkg.pm +FS/cust_bill_pkg_detail.pm FS/cust_credit.pm FS/cust_credit_bill.pm FS/cust_main.pm @@ -127,6 +128,7 @@ t/cust_bill.t t/cust_bill_event.t t/cust_bill_pay.t t/cust_bill_pkg.t +t/cust_bill_pkg_detail.t t/cust_credit.t t/cust_credit_bill.t t/cust_credit_refund.t diff --git a/FS/bin/freeside-setup b/FS/bin/freeside-setup index 19483765e..cf009c249 100755 --- a/FS/bin/freeside-setup +++ b/FS/bin/freeside-setup @@ -421,6 +421,18 @@ sub tables_hash_hack { 'index' => [ ['invnum'] ], }, + 'cust_bill_pkg_detail' => { + 'columns' => [ + 'detailnum', 'serial', '', '', + 'pkgnum', 'int', '', '', + 'invnum', 'int', '', '', + 'detail', 'varchar', '', $char_d, + ], + 'primary_key' => 'detailnum', + 'unique' => [], + 'index' => [ [ 'pkgnum', 'invnum' ] ], + }, + 'cust_credit' => { 'columns' => [ 'crednum', 'serial', '', '', diff --git a/FS/t/cust_bill_pkg_detail.t b/FS/t/cust_bill_pkg_detail.t new file mode 100644 index 000000000..ea6e3d125 --- /dev/null +++ b/FS/t/cust_bill_pkg_detail.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cust_bill_pkg_detail; +$loaded=1; +print "ok 1\n"; diff --git a/httemplate/docs/schema.html b/httemplate/docs/schema.html index b4d21f3b9..6522b041a 100644 --- a/httemplate/docs/schema.html +++ b/httemplate/docs/schema.html @@ -59,6 +59,13 @@
  • edate - ending date
  • itemdesc - Line item description (currently used only when pkgnum is 0) +
  • cust_bill_pkg_detail - Invoice line items detail +
      +
    • detailnum - primary key +
    • pkgnum - +
    • invnum - +
    • detail - Detail description +
  • cust_credit - Credits. The equivalent of a negative cust_bill record.
    • crednum - primary key diff --git a/httemplate/docs/upgrade10.html b/httemplate/docs/upgrade10.html new file mode 100644 index 000000000..7aa26f698 --- /dev/null +++ b/httemplate/docs/upgrade10.html @@ -0,0 +1,11 @@ +this is very incomplete + +CREATE TABLE cust_bill_pkg_detail ( + detailnum serial, + pkgnum int NOT NULL, + invnum int NOT NULL, + detail varchar(80), + PRIMARY KEY (detailnum) +); +CREATE INDEX cust_bill_pkg_detail1 ON cust_bill_pkg_detail ( pkgnum, invnum ); + diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index 1fd634961..851d3aa4d 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -395,7 +395,7 @@ tie my %plans, 'Tie::IxHash', }, 'fieldorder' => [qw( setup_fee recur_flat recur_included_hours recur_hourly_charge recur_included_input recur_input_charge recur_included_output recur_output_charge recur_included_total recur_total_charge )], 'setup' => 'what.setup_fee.value', - 'recur' => '\'my $last_bill = $cust_pkg->last_bill; my $hours = $cust_pkg->seconds_since_sqlradacct($last_bill, $sdate ) / 3600 - \' + what.recur_included_hours.value + \'; $hours = 0 if $hours < 0; my $input = $cust_pkg->attribute_since_sqlradacct($last_bill, $sdate, \"AcctInputOctets\" ) / 1048576; my $output = $cust_pkg->attribute_since_sqlradacct($last_bill, $sdate, \"AcctOutputOctets\" ) / 1048576; my $total = $input + $output - \' + what.recur_included_total.value + \'; $total = 0 if $total < 0; my $input = $input - \' + what.recur_included_input.value + \'; $input = 0 if $input < 0; my $output = $output - \' + what.recur_included_output.value + \'; $output = 0 if $output < 0; \' + what.recur_flat.value + \' + \' + what.recur_hourly_charge.value + \' * $hours + \' + what.recur_input_charge.value + \' * $input + \' + what.recur_output_charge.value + \' * $output + \' + what.recur_total_charge.value + \' * $total ;\'', + 'recur' => '\'my $last_bill = $cust_pkg->last_bill; my $hours = $cust_pkg->seconds_since_sqlradacct($last_bill, $sdate ) / 3600 - \' + what.recur_included_hours.value + \'; $hours = 0 if $hours < 0; my $input = $cust_pkg->attribute_since_sqlradacct($last_bill, $sdate, \"AcctInputOctets\" ) / 1048576; my $output = $cust_pkg->attribute_since_sqlradacct($last_bill, $sdate, \"AcctOutputOctets\" ) / 1048576; my $total = $input + $output - \' + what.recur_included_total.value + \'; $total = 0 if $total < 0; my $input = $input - \' + what.recur_included_input.value + \'; $input = 0 if $input < 0; my $output = $output - \' + what.recur_included_output.value + \'; $output = 0 if $output < 0; my $totalcharge = sprintf(\"%.2f\", \' + what.recur_total_charge.value + \' * $total); my $hourscharge = sprintf(\"%.2f\", \' + what.recur_hourly_charge.value + \' * $hours); push @details, \"Last month\\\'s excess data \". sprintf(\"%.1f\", $total). \" megs: \\\$$totalcharge\", \"Last month\\\'s excess time \". sprintf(\"%.1f\", $hours). \" hours: \\\$$hourscharge\"; \' + what.recur_flat.value + \' + $hourscharge + \' + what.recur_input_charge.value + \' * $input + \' + what.recur_output_charge.value + \' * $output + $totalcharge ;\'', }, ; @@ -485,11 +485,11 @@ my $widget = new HTML::Widgets::SelectLayers( ''. 'Setup expression
      '. ''. + encode_entities($hashref->{setup}). '" onLoad="fchanged(this)">'. '

      '. 'Recurring espression
      '. ''. + encode_entities($hashref->{recur}). '" onLoad="fchanged(this)">'. '
      '. ''. ''; -- cgit v1.2.1 From 2fc87c32cbb509992070b044ed66e2a0e9828a17 Mon Sep 17 00:00:00 2001 From: khoff Date: Wed, 12 Feb 2003 01:21:50 +0000 Subject: s/;/,/ --- FS/bin/freeside-setup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/bin/freeside-setup b/FS/bin/freeside-setup index cf009c249..b3cdb3384 100755 --- a/FS/bin/freeside-setup +++ b/FS/bin/freeside-setup @@ -1039,7 +1039,7 @@ sub tables_hash_hack { 'columns' => [ 'svcpart', 'int', '', '', 'routernum', 'int', '', '', - ]; + ], 'primary_key' => '', 'unique' => [], 'index' => [], -- cgit v1.2.1 From 7a168617da5f6702422c098d714a11586d7655e9 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 25 Feb 2003 05:40:36 +0000 Subject: remove max length on ACH account number --- httemplate/edit/cust_main.cgi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi index 5642604d2..6fc086a12 100755 --- a/httemplate/edit/cust_main.cgi +++ b/httemplate/edit/cust_main.cgi @@ -412,7 +412,7 @@ if ( $payby_default eq 'HIDE' ) { my %paybychecked = ( 'CARD' => qq!Credit card
      ${r}
      ${r}Exp !. expselect("CARD", $cust_main->paydate). qq!
      ${r}Name on card
      !, - 'CHEK' => qq!Electronic check
      ${r}Account number
      ${r}ABA/Routing code
      ${r}Bank name !, + 'CHEK' => qq!Electronic check
      ${r}Account number
      ${r}ABA/Routing code
      ${r}Bank name !, 'LECB' => qq!Phone bill billing
      ${r}Phone number !, 'BILL' => qq!Billing
      P.O.
      Attention
      !, 'COMP' => qq!Complimentary
      ${r}Approved by
      ${r}Exp !. expselect("COMP", $cust_main->paydate), -- cgit v1.2.1 From 47acc8a99bd1fcdba4fce00e1838926046cd2f81 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 1 Mar 2003 03:15:01 +0000 Subject: change next bill date comparison from < to <= --- FS/FS/cust_main.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 807fadbcc..6331fda6d 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -979,7 +979,7 @@ sub bill { my $sdate; if ( $part_pkg->getfield('freq') > 0 && ! $cust_pkg->getfield('susp') && - ( $cust_pkg->getfield('bill') || 0 ) < $time + ( $cust_pkg->getfield('bill') || 0 ) <= $time ) { my $recur_prog = $part_pkg->getfield('recur'); $recur_prog =~ /^(.*)$/ or do { -- cgit v1.2.1 From 97320dbd0747826bc4082a466cfe0ea4e8168628 Mon Sep 17 00:00:00 2001 From: khoff Date: Tue, 4 Mar 2003 01:56:10 +0000 Subject: updated for svc_broadband changes --- FS/MANIFEST | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/FS/MANIFEST b/FS/MANIFEST index e8b1da7a7..782286544 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -44,9 +44,6 @@ FS/UI/Gtk.pm FS/UI/agent.pm FS/UID.pm FS/Msgcat.pm -FS/ac.pm -FS/ac_block.pm -FS/ac_type.pm FS/agent.pm FS/agent_type.pm FS/cust_bill.pm @@ -66,7 +63,6 @@ FS/cust_refund.pm FS/cust_credit_refund.pm FS/cust_svc.pm FS/part_bill_event.pm -FS/part_ac_field.pm FS/export_svc.pm FS/part_export.pm FS/part_export_option.pm @@ -93,12 +89,17 @@ FS/part_pop_local.pm FS/part_referral.pm FS/part_svc.pm FS/part_svc_column.pm +FS/part_router_field.pm +FS/part_sb_field.pm +FS/part_svc_router.pm FS/pkg_svc.pm FS/svc_Common.pm FS/svc_acct.pm FS/svc_acct_pop.pm FS/svc_broadband.pm FS/svc_domain.pm +FS/router.pm +FS/router_field.pm FS/type_pkgs.pm FS/nas.pm FS/port.pm @@ -107,6 +108,7 @@ FS/domain_record.pm FS/prepay_credit.pm FS/svc_www.pm FS/svc_forward.pm +FS/sb_field.pm FS/raddb.pm FS/radius_usergroup.pm FS/queue.pm -- cgit v1.2.1 From 8df8fb820c0cd6559ee6fef3adffaf43df167353 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 11 Mar 2003 10:41:52 +0000 Subject: another pg7.3 fix --- httemplate/edit/part_pkg.cgi | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index 851d3aa4d..dee356250 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -131,11 +131,12 @@ my $columns = 3; my @part_svc = qsearch( 'part_svc', { 'disabled' => '' } ); foreach my $part_svc ( @part_svc ) { my $svcpart = $part_svc->svcpart; - my $pkg_svc = qsearchs( 'pkg_svc', { - 'pkgpart' => $cgi->param('clone') || $part_pkg->pkgpart, + my $pkgpart = $cgi->param('clone') || $part_pkg->pkgpart; + my $pkg_svc = $pkgpart && qsearchs( 'pkg_svc', { + 'pkgpart' => $pkgpart, 'svcpart' => $svcpart, } ) || new FS::pkg_svc ( { - 'pkgpart' => $cgi->param('clone') || $part_pkg->pkgpart, + 'pkgpart' => $pkgpart, 'svcpart' => $svcpart, 'quantity' => 0, }); -- cgit v1.2.1 From c174642d32c139d1233597e3fa6a467586207023 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 11 Mar 2003 11:36:51 +0000 Subject: minor meta import webUI updates --- httemplate/misc/meta-import.cgi | 24 +++++++--- httemplate/misc/process/meta-import.cgi | 82 +++++++++++++-------------------- 2 files changed, 50 insertions(+), 56 deletions(-) diff --git a/httemplate/misc/meta-import.cgi b/httemplate/misc/meta-import.cgi index ebd2a7e2f..2f3b7380d 100644 --- a/httemplate/misc/meta-import.cgi +++ b/httemplate/misc/meta-import.cgi @@ -1,9 +1,7 @@ <%= header('Import') %>
      -Import a CSV file containing customer records (zip tar etc).

      - -##no##Default file format is CSV, with the following field order: cust_pkg.setup, dayphone, first, last, address1, address2, city, state, zip, comments

      +Import data from a DBI data source

      <% #false laziness with edit/cust_main.cgi @@ -44,9 +42,23 @@ Import a CSV file containing customer records (zip tar etc).

      <% } %>

      - CSV (zip, tar etc) Filename:

      - -
      + + + + + + + + + + + + + +
      DBI data source:
      DBI username:
      DBI password:
      + + + diff --git a/httemplate/misc/process/meta-import.cgi b/httemplate/misc/process/meta-import.cgi index 4c3c7d665..9e374b508 100644 --- a/httemplate/misc/process/meta-import.cgi +++ b/httemplate/misc/process/meta-import.cgi @@ -1,5 +1,6 @@ <%= header('Map tables') %> + +
      + <% - #one - unless ( $cgi->param('magic') ) { - - #oops, silly - #my $fh = $cgi->upload('csvfile'); - ##warn $cgi; - ##warn $fh; - # - #use Archive::Tar; - #$tar = Archive::Tar->new(); - #$tar->create_archive($fh); #or die $tar->error; - - #haha for now - my @files = qw( -authserv credtype dunprev invoice pmtdet product taxplan -ccdet customer genlog ledger pops pubvars -cchist discplan glacct origco prodcat recur users -credcode dundet invline payment prodclas repforms webserv - ); + #use DBIx::DBSchema; + my $schema = new_native DBIx::DBSchema + map { $cgi->param($_) } qw( data_source username password ); + foreach my $field (qw( data_source username password )) { %> + VALUE="<%= $cgi->param($field) %>"> + <% } + + my %schema = (); + if ( $cgi->param('schema') ) { + my $schema_string = $cgi->param('schema'); + %> <% + %schema = map { /^\s*(\w+)\s*=>\s*(\w+)\s*$/ + or die "guru meditation #420: $_"; + ( $1 => $2 ); + } + split( /\n/, $schema_string ); + } + + #first page + unless ( $cgi->param('magic') ) { %> - %> - <%= hashmaker('schema', \@files, [ grep { ! /^h_/ } dbdef->tables ] ) %> + <%= hashmaker('schema', [ $schema->tables ], + [ grep !/^h_/, dbdef->tables ], ) %>
      <% - } elsif ( $cgi->param('magic') eq 'process' ) { + #second page + } elsif ( $cgi->param('magic') eq 'process' ) { %> - %> <% - my $schema_string = $cgi->param('schema'); - %><% - my %schema = map { /^\s*(\w+)\s*=>\s*(\w+)\s*$/ - or die "guru meditation #420: $_"; - ( $1 => $2 ); - } - split( /\n/, $schema_string ); - - #*** should be in global.asa/handler.pl like the rest - eval 'use Text::CSV_XS;'; - foreach my $table ( keys %schema ) { - my $csv = Text::CSV_XS->new({ 'binary'=>1 }); - open(FILE,"); - close FILE; - $csv->parse($header) or die; - my @from_columns = $csv->fields; - + my @from_columns = $schema->table($table)->columns; my @fs_columns = dbdef->table($schema{$table})->columns; %> - <%= hashmaker($table, \@from_columns, \@fs_columns, $table, $schema{$table} ) %> + <%= hashmaker($table, \@from_columns => \@fs_columns, + $table => $schema{$table}, ) %>


      <% @@ -87,16 +74,11 @@ credcode dundet invline payment prodclas repforms webserv
      <% + #third (results) } elsif ( $cgi->param('magic') eq 'process2' ) { print "
      \n";
      -    #false laziness with above
      -    my $schema_string = $cgi->param('schema');
      -    my %schema = map { /^\s*(\w+)\s*=>\s*(\w+)\s*$/
      -                         or die "guru meditation #420: $_";
      -                       ( $1 => $2 );
      -                     }
      -                 split( /\n/, $schema_string );
      +
           foreach my $table ( keys %schema ) {
             ( my $spaces = $table ) =~ s/./ /g;
             print "'$table' => { 'table' => '$schema{$table}',\n".
      -- 
      cgit v1.2.1
      
      
      From 0857c5465840060a13b93e6c5a15af6ed6476a1f Mon Sep 17 00:00:00 2001
      From: ivan 
      Date: Tue, 11 Mar 2003 11:40:35 +0000
      Subject: another pg7.3 fix
      
      ---
       FS/FS/svc_domain.pm | 6 ++++--
       1 file changed, 4 insertions(+), 2 deletions(-)
      
      diff --git a/FS/FS/svc_domain.pm b/FS/FS/svc_domain.pm
      index 0d71b2775..3941d6eff 100644
      --- a/FS/FS/svc_domain.pm
      +++ b/FS/FS/svc_domain.pm
      @@ -382,8 +382,10 @@ sub check {
         $recref->{action} =~ /^(M|N)$/ or return "Illegal action";
         $recref->{action} = $1;
       
      -  my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $recref->{catchall} } );
      -  return "Unknown catchall" unless $svc_acct || ! $recref->{catchall};
      +  if ( $recref->{catchall} ne '' ) {
      +    my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $recref->{catchall} } );
      +    return "Unknown catchall" unless $svc_acct;
      +  }
       
         $self->ut_textn('purpose');
       
      -- 
      cgit v1.2.1
      
      
      From 6609285a50f6c9110ae8d5a26a767fd895f1e325 Mon Sep 17 00:00:00 2001
      From: ivan 
      Date: Wed, 12 Mar 2003 00:44:23 +0000
      Subject: doc from 1.4 branch
      
      ---
       httemplate/docs/upgrade9.html | 5 +++++
       1 file changed, 5 insertions(+)
      
      diff --git a/httemplate/docs/upgrade9.html b/httemplate/docs/upgrade9.html
      index da5b643fb..c6afe2b0f 100644
      --- a/httemplate/docs/upgrade9.html
      +++ b/httemplate/docs/upgrade9.html
      @@ -14,6 +14,11 @@
       
       INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 18, 'daytime', 'en_US', 'Day Phone' );
       INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 19, 'night', 'en_US', 'Night Phone' );
      +
      +
    • Optionally, apply the following changes to your database (performance improvement for large numbers of services or packages): +
      +CREATE INDEX part_pkg1 ON part_pkg ( disabled );
      +CREATE INDEX part_svc1 ON part_svc ( disabled );
       
    • If you want to use ACH (electronic checks), you will need to make changes to your database. The easiest way to make these changes is to dump your database (with pg_dump), change the payinfo field in the cust_pay, cust_refund, h_cust_pay and h_cust_refund tables from varchar(16) to varchar(80), reload the database from the dump, and run dbdef-create
    • Restart Apache and freeside-queued. -- cgit v1.2.1 From 9aec22e5fd00800c6e7952ae5b85cc639d4b1e78 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 20 Mar 2003 03:41:03 +0000 Subject: apache export! --- FS/FS/domain_record.pm | 4 +-- FS/FS/part_export.pm | 28 ++++++++++++++++++- FS/FS/part_export/apache.pm | 7 +++++ FS/MANIFEST | 1 + FS/t/part_export-apache.t | 5 ++++ bin/apache.export | 65 +++++++++++++++++++++++++++++++++++++++++++++ bin/bind.export | 7 +---- bin/bsdshell.export | 7 +---- bin/sysvshell.export | 7 +---- 9 files changed, 110 insertions(+), 21 deletions(-) create mode 100644 FS/FS/part_export/apache.pm create mode 100644 FS/t/part_export-apache.t create mode 100755 bin/apache.export diff --git a/FS/FS/domain_record.pm b/FS/FS/domain_record.pm index 37cc6c9e8..3297e6bfb 100644 --- a/FS/FS/domain_record.pm +++ b/FS/FS/domain_record.pm @@ -261,7 +261,7 @@ sub check { or return "Illegal data for PTR record: ". $self->recdata; $self->recdata($1); } elsif ( $self->rectype eq 'CNAME' ) { - $self->recdata =~ /^([a-z0-9\.\-]+)$/i + $self->recdata =~ /^([a-z0-9\.\-]+|\@)$/i or return "Illegal data for CNAME record: ". $self->recdata; $self->recdata($1); } elsif ( $self->rectype eq '_mstr' ) { @@ -313,7 +313,7 @@ sub svc_domain { =head1 VERSION -$Id: domain_record.pm,v 1.11 2002-06-23 19:16:45 ivan Exp $ +$Id: domain_record.pm,v 1.12 2003-03-20 03:41:03 ivan Exp $ =head1 BUGS diff --git a/FS/FS/part_export.pm b/FS/FS/part_export.pm index d62bef50d..b46d8439e 100644 --- a/FS/FS/part_export.pm +++ b/FS/FS/part_export.pm @@ -604,7 +604,7 @@ tie my %shellcommands_withdomain_options, 'Tie::IxHash', ; tie my %www_shellcommands_options, 'Tie::IxHash', - 'user' => { lable=>'Remote username', default=>'root' }, + 'user' => { label=>'Remote username', default=>'root' }, 'useradd' => { label=>'Insert command', default=>'mkdir /var/www/$zone; chown $username /var/www/$zone; ln -s /var/www/$zone $homedir/$zone', }, @@ -616,6 +616,27 @@ tie my %www_shellcommands_options, 'Tie::IxHash', }, ; +tie my %apache_options, 'Tie::IxHash', + 'user' => { label=>'Remote username', default=>'root' }, + 'httpd_conf' => { label=>'httpd.conf snippet location', + default=>'/etc/apache/httpd-freeside.conf', }, + 'template' => { + label => 'Template', + type => 'textarea', + default => <<'END', + #generic +# #preferred, http://httpd.apache.org/docs/dns-caveats.html +DocumentRoot /var/www/$zone +ServerName $zone +ServerAlias *.$zone +#BandWidthModule On +#LargeFileLimit 4096 12288 + + +END + }, +; + tie my %domain_shellcommands_options, 'Tie::IxHash', 'user' => { lable=>'Remote username', default=>'root' }, 'useradd' => { label=>'Insert command', @@ -894,6 +915,11 @@ tie my %ldap_options, 'Tie::IxHash', 'notes' => 'Run remote commands via SSH, for virtual web sites. You will need to setup SSH for unattended operation.', }, + 'apache' => { + 'desc' => 'Export an Apache httpd.conf file snippet.', + 'options' => \%apache_options, + 'notes' => 'Batch export of an httpd.conf snippet from a template. Typically used with something like Include /etc/apache/httpd-freeside.conf in httpd.conf. File::Rsync must be installed. Run bin/apache.export to export the files.', + }, }, 'svc_broadband' => { diff --git a/FS/FS/part_export/apache.pm b/FS/FS/part_export/apache.pm new file mode 100644 index 000000000..b72c9bdb0 --- /dev/null +++ b/FS/FS/part_export/apache.pm @@ -0,0 +1,7 @@ +package FS::part_export::bind; + +use vars qw(@ISA); +use FS::part_export::null; + +@ISA = qw(FS::part_export::null); + diff --git a/FS/MANIFEST b/FS/MANIFEST index 782286544..32a4e4f59 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -66,6 +66,7 @@ FS/part_bill_event.pm FS/export_svc.pm FS/part_export.pm FS/part_export_option.pm +FS/part_export/apache.pm FS/part_export/bind.pm FS/part_export/bind_slave.pm FS/part_export/bsdshell.pm diff --git a/FS/t/part_export-apache.t b/FS/t/part_export-apache.t new file mode 100644 index 000000000..b9995080f --- /dev/null +++ b/FS/t/part_export-apache.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::part_export::apache; +$loaded=1; +print "ok 1\n"; diff --git a/bin/apache.export b/bin/apache.export new file mode 100755 index 000000000..6142c92b4 --- /dev/null +++ b/bin/apache.export @@ -0,0 +1,65 @@ +#!/usr/bin/perl -w + +use strict; +#use File::Path; +use File::Rsync; +use Net::SSH qw(ssh); +use FS::UID qw(adminsuidsetup datasrc); +use FS::Record qw(qsearch qsearchs); +use FS::part_export; +use FS::cust_svc; +use FS::svc_www; + +my $user = shift or die &usage; +adminsuidsetup $user; + +#needs the export number in there somewhere too...? +my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/apache"; +mkdir $spooldir, 0700 unless -d $spooldir; + +my @exports = qsearch('part_export', { 'exporttype' => 'apache' } ); + +my $rsync = File::Rsync->new({ + rsh => 'ssh', +# dry_run => 1, +}); + +foreach my $export ( @exports ) { + + my $machine = $export->machine; + my $file = "$spooldir/$machine.conf"; + + open(HTTPD_CONF,">$file") or die "can't open $file: $!"; + + my $template = $export->option('template'); + + my @svc_www = $export->svc_x; + + foreach my $svc_www ( @svc_www ) { + use vars qw($zone $username); + $zone = $svc_www->domain_record->zone; + $username = $svc_www->svc_acct->username; + print HTTPD_CONF eval(qq("$template")); + } + + my $user = $export->option('user'); + my $httpd_conf = $export->option('httpd_conf') + + $rsync->exec( { + src => $file, + dest => "$user\@$machine:$httpd_conf", + } ) or die "rsync to $machine failed: ". join(" / ", $rsync->err); + # warn $rsync->out; + + ssh("root\@$machine", 'ndc reload'); + +} + +close NAMED_CONF; + +# ----- + +sub usage { + die "Usage:\n export.export user\n"; +} + diff --git a/bin/bind.export b/bin/bind.export index 943aefbb0..055782a20 100755 --- a/bin/bind.export +++ b/bin/bind.export @@ -46,12 +46,7 @@ foreach my $export ( @exports ) { my $zonepath = $export->option('zonepath'); $zonepath =~ s/\/$//; - #false laziness with freeside-sqlradius-reset and shell.export - my @svc_domain = - map { qsearchs('svc_domain', { 'svcnum' => $_->svcnum } ) } - map { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) } - grep { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) } - $export->export_svc; + my @svc_domain = $export->svc_x; foreach my $svc_domain ( @svc_domain ) { my $domain = $svc_domain->domain; diff --git a/bin/bsdshell.export b/bin/bsdshell.export index 10c27671e..6e0d1037e 100755 --- a/bin/bsdshell.export +++ b/bin/bsdshell.export @@ -43,12 +43,7 @@ foreach my $export ( @bsd_exports ) { chmod 0644, "$prefix/passwd"; chmod 0600, "$prefix/master.passwd"; - #false laziness with freeside-sqlradius-reset and bind.export - my @svc_acct = - map { qsearchs('svc_acct', { 'svcnum' => $_->svcnum } ) } - map { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) } - grep { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) } - $export->export_svc; + my @svc_acct = $export->svc_x; next unless @svc_acct; diff --git a/bin/sysvshell.export b/bin/sysvshell.export index 859766103..c13912c3f 100755 --- a/bin/sysvshell.export +++ b/bin/sysvshell.export @@ -43,12 +43,7 @@ foreach my $export ( @sysv_exports ) { chmod 0644, "$prefix/passwd"; chmod 0600, "$prefix/shadow"; - #false laziness with freeside-sqlradius-reset and bind.export - my @svc_acct = - map { qsearchs('svc_acct', { 'svcnum' => $_->svcnum } ) } - map { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) } - grep { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) } - $export->export_svc; + my @svc_acct = $export->svc_x; next unless @svc_acct; -- cgit v1.2.1