From: ivan Date: Fri, 29 Dec 2006 08:51:34 +0000 (+0000) Subject: service refactor! X-Git-Tag: TRIXBOX_2_6~787 X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=633c48448d9468690b7ad77eb6ff7c660a286658 service refactor! --- diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index c25b9beb0..ba0309152 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -974,21 +974,9 @@ returns the error, otherwise returns false. =cut sub replace { - my $new = shift; - my $old = shift; - - if (!defined($old)) { - warn "[debug]$me replace called with no arguments; autoloading old record\n" - if $DEBUG; - my $primary_key = $new->dbdef_table->primary_key; - if ( $primary_key ) { - $old = qsearchs($new->table, { $primary_key => $new->$primary_key() } ) - or croak "can't find ". $new->table. ".$primary_key ". - $new->$primary_key(); - } else { - croak $new->table. " has no primary key; pass old record as argument"; - } - } + my ($new, $old) = (shift, shift); + + $old = $new->replace_old unless defined($old); warn "[debug]$me $new ->replace $old\n" if $DEBUG; @@ -1158,6 +1146,22 @@ sub replace { } +sub replace_old { + my( $self ) = shift; + warn "[$me] replace called with no arguments; autoloading old record\n" + if $DEBUG; + + my $primary_key = $self->dbdef_table->primary_key; + if ( $primary_key ) { + $self->by_key( $self->$primary_key() ) #this is what's returned + or croak "can't find ". $self->table. ".$primary_key ". + $self->$primary_key(); + } else { + croak $self->table. " has no primary key; pass old record as argument"; + } + +} + =item rep Depriciated (use replace instead). diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 255b34406..33baa0a88 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -867,13 +867,20 @@ sub tables_hashref { 'svc_domain' => { 'columns' => [ - 'svcnum', 'int', '', '', '', '', - 'domain', 'varchar', '', $char_d, '', '', - 'catchall', 'int', 'NULL', '', '', '', + 'svcnum', 'int', '', '', '', '', + 'domain', 'varchar', '', $char_d, '', '', + 'suffix', 'varchar', 'NULL', $char_d, '', '', + 'catchall', 'int', 'NULL', '', '', '', + 'parent_svcnum', 'int', 'NULL', '', '', '', + 'registrarnum', 'int', 'NULL', '', '', '', + 'registrarkey', 'varchar', 'NULL', '', '', '', + 'setup_date', @date_type, '', '', + 'renewal_interval', 'int', 'NULL', '', '', '', + 'expiration_date', @date_type, '', '', ], 'primary_key' => 'svcnum', - 'unique' => [ ['domain'] ], - 'index' => [], + 'unique' => [ ], + 'index' => [ ['domain'] ], }, 'domain_record' => { @@ -890,6 +897,16 @@ sub tables_hashref { 'index' => [ ['svcnum'] ], }, + 'registrar' => { + 'columns' => [ + 'registrarnum', 'serial', '', '', '', '', + 'registrarname', 'varchar', '', $char_d, '', '', + ], + 'primary_key' => 'registrarnum', + 'unique' => [], + 'index' => [], + }, + 'svc_forward' => { 'columns' => [ 'svcnum', 'int', '', '', '', '', diff --git a/FS/FS/UI/Web.pm b/FS/FS/UI/Web.pm index 0597a385e..3348d670e 100644 --- a/FS/FS/UI/Web.pm +++ b/FS/FS/UI/Web.pm @@ -1,6 +1,7 @@ package FS::UI::Web; -use vars qw($DEBUG); +use strict; +use vars qw($DEBUG $me); use FS::Conf; use FS::Record qw(dbdef); @@ -9,6 +10,11 @@ use FS::Record qw(dbdef); #@ISA = qw( FS::UI ); $DEBUG = 0; +$me = '[FS::UID::Web]'; + +### +# date parsing +### use Date::Parse; sub parse_beginning_ending { @@ -32,6 +38,109 @@ sub parse_beginning_ending { ( $beginning, $ending ); } +=item svc_url + +Returns a service URL, first checking to see if there is a service-specific +page to link to, otherwise to a generic service handling page. Options are +passed as a list of name-value pairs, and include: + +=over 4 + +=item * m - Mason request object ($m) + +=item * action - The action for which to construct "edit", "view", or "search" + +=item ** part_svc - Service definition (see L) + +=item ** svcdb - Service table + +=item *** query - Query string + +=item *** svc - FS::cust_svc or FS::svc_* object + +=item ahref - Optional flag, if set true returns instead of just the URL. + +=back + +* Required fields + +** part_svc OR svcdb is required + +*** query OR svc is required + +=cut + + # ## + # #required + # ## + # 'm' => $m, #mason request object + # 'action' => 'edit', #or 'view' + # + # 'part_svc' => $part_svc, #usual + # #OR + # 'svcdb' => 'svc_table', + # + # 'query' => #optional query string + # #OR + # 'svc' => $svc_x, #or $cust_svc, it just needs a svcnum + # + # ## + # #optional + # ## + # 'ahref' => 1, # if set true, returns + +use FS::CGI qw(popurl); +sub svc_url { + my %opt = @_; + + #? return '' unless ref($opt{part_svc}); + + my $svcdb = $opt{svcdb} || $opt{part_svc}->svcdb; + my $query = exists($opt{query}) ? $opt{query} : $opt{svc}->svcnum; + my $url; + warn "$me [svc_url] checking for /$opt{action}/$svcdb.cgi component" + if $DEBUG; + if ( $opt{m}->interp->comp_exists("/$opt{action}/$svcdb.cgi") ) { + $url = "$svcdb.cgi?"; + } else { + + my $generic = $opt{action} eq 'search' ? 'cust_svc' : 'svc_Common'; + + $url = "$generic.html?svcdb=$svcdb;"; + $url .= 'svcnum=' if $query =~ /^\d+(;|$)/; + } + + my $p = popurl(2); #? + my $return = "$p$opt{action}/$url$query"; + + $return = qq!! if $opt{ahref}; + + $return; +} + +sub svc_link { + my($m, $part_svc, $cust_svc) = @_ or return ''; + svc_X_link( $part_svc->svc, @_ ); +} + +sub svc_label_link { + my($m, $part_svc, $cust_svc) = @_ or return ''; + svc_X_link( ($cust_svc->label)[1], @_ ); +} + +sub svc_X_link { + my ($x, $m, $part_svc, $cust_svc) = @_ or return ''; + my $ahref = svc_url( + 'ahref' => 1, + 'm' => $m, + 'action' => 'view', + 'part_svc' => $part_svc, + 'svc' => $cust_svc, + ); + + "$ahref$x"; +} + sub parse_lt_gt { my($cgi, $field) = @_; diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index ebe4c242e..32da745fe 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -1526,11 +1526,17 @@ Returns all packages (see L) for this customer. sub all_pkgs { my $self = shift; + + return $self->num_pkgs unless wantarray; + + my @cust_pkg = (); if ( $self->{'_pkgnum'} ) { - values %{ $self->{'_pkgnum'}->cache }; + @cust_pkg = values %{ $self->{'_pkgnum'}->cache }; } else { - qsearch( 'cust_pkg', { 'custnum' => $self->custnum }); + @cust_pkg = qsearch( 'cust_pkg', { 'custnum' => $self->custnum }); } + + sort sort_packages @cust_pkg; } =item ncancelled_pkgs @@ -1541,19 +1547,43 @@ Returns all non-cancelled packages (see L) for this customer. sub ncancelled_pkgs { my $self = shift; + + return $self->num_ncancelled_pkgs unless wantarray; + + my @cust_pkg = (); if ( $self->{'_pkgnum'} ) { - grep { ! $_->getfield('cancel') } values %{ $self->{'_pkgnum'}->cache }; + + @cust_pkg = grep { ! $_->getfield('cancel') } + values %{ $self->{'_pkgnum'}->cache }; + } else { - @{ [ # force list context + + @cust_pkg = qsearch( 'cust_pkg', { - 'custnum' => $self->custnum, - 'cancel' => '', - }), + 'custnum' => $self->custnum, + 'cancel' => '', + }); + push @cust_pkg, qsearch( 'cust_pkg', { - 'custnum' => $self->custnum, - 'cancel' => 0, - }), - ] }; + 'custnum' => $self->custnum, + 'cancel' => 0, + }); + } + + sort sort_packages @cust_pkg; + +} + +# This should be generalized to use config options to determine order. +sub sort_packages { + if ( $a->get('cancel') and $b->get('cancel') ) { + $a->pkgnum <=> $b->pkgnum; + } elsif ( $a->get('cancel') or $b->get('cancel') ) { + return -1 if $b->get('cancel'); + return 1 if $a->get('cancel'); + return 0; + } else { + $a->pkgnum <=> $b->pkgnum; } } @@ -1602,8 +1632,11 @@ customer. =cut sub num_cancelled_pkgs { - my $self = shift; - $self->num_pkgs("cancel IS NOT NULL AND cust_pkg.cancel != 0"); + shift->num_pkgs("cust_pkg.cancel IS NOT NULL AND cust_pkg.cancel != 0"); +} + +sub num_ncancelled_pkgs { + shift->num_pkgs("( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )"); } sub num_pkgs { @@ -1737,7 +1770,7 @@ sub _banned_pay_hashref { { 'payby' => $payby2ban{$self->payby}, 'payinfo' => md5_base64($self->payinfo), - #'reason' => + #don't ever *search* on reason! #'reason' => }; } diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 6807baf49..689ffedd0 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -1,7 +1,7 @@ package FS::cust_pkg; use strict; -use vars qw(@ISA $disable_agentcheck @SVCDB_CANCEL_SEQ $DEBUG); +use vars qw(@ISA $disable_agentcheck $DEBUG); use Tie::IxHash; use FS::UID qw( getotaker dbh ); use FS::Misc qw( send_email ); @@ -15,6 +15,7 @@ use FS::pkg_svc; use FS::cust_bill_pkg; use FS::h_cust_svc; use FS::reg_code; +use FS::part_svc; use FS::cust_pkg_reason; # need to 'use' these instead of 'require' in sub { cancel, suspend, unsuspend, @@ -34,14 +35,6 @@ $DEBUG = 0; $disable_agentcheck = 0; -# The order in which to unprovision services. -@SVCDB_CANCEL_SEQ = qw( svc_external - svc_www - svc_forward - svc_acct - svc_domain - svc_broadband ); - sub _cache { my $self = shift; my ( $hashref, $cache ) = @_; @@ -273,6 +266,10 @@ Calls sub replace { my( $new, $old, %options ) = @_; + # We absolutely have to have an old vs. new record to make this work. + if (!defined($old)) { + $old = qsearchs( 'cust_pkg', { 'pkgnum' => $new->pkgnum } ); + } #return "Can't (yet?) change pkgpart!" if $old->pkgpart != $new->pkgpart; return "Can't change otaker!" if $old->otaker ne $new->otaker; @@ -452,19 +449,18 @@ sub cancel { my %svc; foreach my $cust_svc ( - qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } ) + #schwartz + map { $_->[0] } + sort { $a->[1] <=> $b->[1] } + map { [ $_, $_->svc_x->table_info->{'cancel_weight'} ]; } + qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } ) ) { - push @{ $svc{$cust_svc->part_svc->svcdb} }, $cust_svc; - } - foreach my $svcdb (@SVCDB_CANCEL_SEQ) { - foreach my $cust_svc (@{ $svc{$svcdb} }) { - my $error = $cust_svc->cancel; + my $error = $cust_svc->cancel; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "Error cancelling cust_svc: $error"; - } + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "Error cancelling cust_svc: $error"; } } @@ -762,6 +758,17 @@ sub calc_cancel { $self->part_pkg->calc_cancel($self, @_); } +=item cust_bill_pkg + +Returns any invoice line items for this package (see L). + +=cut + +sub cust_bill_pkg { + my $self = shift; + qsearch( 'cust_bill_pkg', { 'pkgnum' => $self->pkgnum } ); +} + =item cust_svc [ SVCPART ] Returns the services for this package, as FS::cust_svc objects (see @@ -843,7 +850,7 @@ sub num_cust_svc { =item available_part_svc -Returns a list FS::part_svc objects representing services included in this +Returns a list of FS::part_svc objects representing services included in this package but not yet provisioned. Each FS::part_svc object also has an extra field, I, which specifies the number of available services. @@ -861,6 +868,86 @@ sub available_part_svc { $self->part_pkg->pkg_svc; } +=item + +Returns a list of FS::part_svc objects representing provisioned and available +services included in this package. Each FS::part_svc object also has the +following extra fields: + +=over 4 + +=item num_cust_svc (count) + +=item num_avail (quantity - count) + +=item cust_pkg_svc (services) - array reference containing the provisioned services, as cust_svc objects + +svcnum +label -> ($cust_svc->label)[1] + +=back + +=cut + +sub part_svc { + my $self = shift; + + #XXX some sort of sort order besides numeric by svcpart... + my @part_svc = sort { $a->svcpart <=> $b->svcpart } map { + my $pkg_svc = $_; + my $part_svc = $pkg_svc->part_svc; + my $num_cust_svc = $self->num_cust_svc($part_svc->svcpart); + $part_svc->{'Hash'}{'num_cust_svc'} = $num_cust_svc; #more evil + $part_svc->{'Hash'}{'num_avail'} = $pkg_svc->quantity - $num_cust_svc; + $part_svc->{'Hash'}{'cust_pkg_svc'} = [ $self->cust_svc($part_svc->svcpart) ]; + $part_svc; + } $self->part_pkg->pkg_svc; + + #extras + push @part_svc, map { + my $part_svc = $_; + my $num_cust_svc = $self->num_cust_svc($part_svc->svcpart); + $part_svc->{'Hash'}{'num_cust_svc'} = $num_cust_svc; #speak no evail + $part_svc->{'Hash'}{'num_avail'} = 0; #0-$num_cust_svc ? + $part_svc->{'Hash'}{'cust_pkg_svc'} = [ $self->cust_svc($part_svc->svcpart) ]; + $part_svc; + } $self->extra_part_svc; + + @part_svc; + +} + +=item extra_part_svc + +Returns a list of FS::part_svc objects corresponding to services in this +package which are still provisioned but not (any longer) available in the +package definition. + +=cut + +sub extra_part_svc { + my $self = shift; + + my $pkgnum = $self->pkgnum; + my $pkgpart = $self->pkgpart; + + qsearch( { + 'table' => 'part_svc', + 'hashref' => {}, + 'extra_sql' => "WHERE 0 = ( SELECT COUNT(*) FROM pkg_svc + WHERE pkg_svc.svcpart = part_svc.svcpart + AND pkg_svc.pkgpart = $pkgpart + AND quantity > 0 + ) + AND 0 < ( SELECT count(*) + FROM cust_svc + LEFT JOIN cust_pkg using ( pkgnum ) + WHERE cust_svc.svcpart = part_svc.svcpart + AND pkgnum = $pkgnum + )", + } ); +} + =item status Returns a short status string for this package, currently: diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm index a76076185..cdb34cd71 100644 --- a/FS/FS/cust_svc.pm +++ b/FS/FS/cust_svc.pm @@ -2,24 +2,21 @@ package FS::cust_svc; use strict; use vars qw( @ISA $DEBUG $me $ignore_quantity ); -use Carp qw( carp cluck ); +use Carp; use FS::Conf; use FS::Record qw( qsearch qsearchs dbh ); use FS::cust_pkg; use FS::part_pkg; use FS::part_svc; use FS::pkg_svc; -use FS::svc_acct; -use FS::svc_domain; -use FS::svc_forward; -use FS::svc_broadband; -use FS::svc_phone; -use FS::svc_external; use FS::domain_record; use FS::part_export; use FS::cdr; -@ISA = qw( FS::Record ); +#most FS::svc_ classes are autoloaded in svc_x emthod +use FS::svc_acct; #this one is used in the cache stuff + +@ISA = qw( FS::cust_main_Mixin FS::Record ); $DEBUG = 0; $me = '[cust_svc]'; @@ -289,54 +286,20 @@ sub label { my $self = shift; carp "FS::cust_svc::label called on $self" if $DEBUG; my $svc_x = $self->svc_x - or die "can't find ". $self->part_svc->svcdb. '.svcnum '. $self->svcnum; + or return "can't find ". $self->part_svc->svcdb. '.svcnum '. $self->svcnum; + $self->_svc_label($svc_x); } sub _svc_label { my( $self, $svc_x ) = ( shift, shift ); - my $svcdb = $self->part_svc->svcdb; - - my $tag; - if ( $svcdb eq 'svc_acct' ) { - $tag = $svc_x->email(@_); - } elsif ( $svcdb eq 'svc_forward' ) { - if ( $svc_x->srcsvc ) { - my $svc_acct = $svc_x->srcsvc_acct(@_); - $tag = $svc_acct->email(@_); - } else { - $tag = $svc_x->src; - } - $tag .= '->'; - if ( $svc_x->dstsvc ) { - my $svc_acct = $svc_x->dstsvc_acct(@_); - $tag .= $svc_acct->email(@_); - } else { - $tag .= $svc_x->dst; - } - } elsif ( $svcdb eq 'svc_domain' ) { - $tag = $svc_x->getfield('domain'); - } elsif ( $svcdb eq 'svc_www' ) { - my $domain_record = $svc_x->domain_record(@_); - $tag = $domain_record->zone; - } elsif ( $svcdb eq 'svc_broadband' ) { - $tag = $svc_x->ip_addr; - } elsif ( $svcdb eq 'svc_phone' ) { - $tag = $svc_x->phonenum; #XXX format it better - } elsif ( $svcdb eq 'svc_external' ) { - my $conf = new FS::Conf; - if ( $conf->config('svc_external-display_type') eq 'artera_turbo' ) { - $tag = sprintf('%010d', $svc_x->id). '-'. - substr('0000000000'.uc($svc_x->title), -10); - } else { - $tag = $svc_x->id. ': '. $svc_x->title; - } - } else { - cluck "warning: asked for label of unsupported svcdb; using svcnum"; - $tag = $svc_x->getfield('svcnum'); - } - $self->part_svc->svc, $tag, $svcdb, $self->svcnum; + ( + $self->part_svc->svc, + $svc_x->label(@_), + $self->part_svc->svcdb, + $self->svcnum + ); } @@ -353,7 +316,7 @@ sub svc_x { if ( $svcdb eq 'svc_acct' && $self->{'_svc_acct'} ) { $self->{'_svc_acct'}; } else { - #require "FS/$svcdb.pm"; + require "FS/$svcdb.pm"; warn "$me svc_x: part_svc.svcpart ". $self->part_svc->svcpart. ", so searching for $svcdb.svcnum ". $self->svcnum. "\n" if $DEBUG; diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm index fc5258fff..5b4e54cc2 100644 --- a/FS/FS/part_svc.pm +++ b/FS/FS/part_svc.pm @@ -2,6 +2,7 @@ package FS::part_svc; use strict; use vars qw( @ISA $DEBUG ); +use Tie::IxHash; use FS::Record qw( qsearch qsearchs fields dbh ); use FS::Schema qw( dbdef ); use FS::part_svc_column; @@ -11,7 +12,7 @@ use FS::cust_svc; @ISA = qw(FS::Record); -$DEBUG = 1; +$DEBUG = 0; =head1 NAME @@ -500,6 +501,161 @@ sub svc_x { map { $_->svc_x } $self->cust_svc; } +=back + +=head1 CLASS METHODS + +=over 4 + +=cut + +my $svc_defs; +sub _svc_defs { + + return $svc_defs if $svc_defs; #cache + + my $conf = new FS::Conf; + + #false laziness w/part_pkg.pm::plan_info + + my %info; + foreach my $INC ( @INC ) { + warn "globbing $INC/FS/svc_*.pm\n" if $DEBUG; + foreach my $file ( glob("$INC/FS/svc_*.pm") ) { + + warn "attempting to load service table info from $file\n" if $DEBUG; + $file =~ /\/(\w+)\.pm$/ or do { + warn "unrecognized file in $INC/FS/: $file\n"; + next; + }; + my $mod = $1; + + if ( $mod =~ /^svc_[A-Z]/ or $mod =~ /^svc_acct_pop$/ ) { + warn "skipping FS::$mod" if $DEBUG; + next; + } + + eval "use FS::$mod;"; + if ( $@ ) { + die "error using FS::$mod (skipping): $@\n" if $@; + next; + } + unless ( UNIVERSAL::can("FS::$mod", 'table_info') ) { + warn "FS::$mod has no table_info method; skipping"; + next; + } + + my $info = "FS::$mod"->table_info; + unless ( keys %$info ) { + warn "FS::$mod->table_info doesn't return info, skipping\n"; + next; + } + warn "got info from FS::$mod: $info\n" if $DEBUG; + if ( exists($info->{'disabled'}) && $info->{'disabled'} ) { + warn "skipping disabled service FS::$mod" if $DEBUG; + next; + } + $info{$mod} = $info; + } + } + + tie my %svc_defs, 'Tie::IxHash', + map { $_ => $info{$_}->{'fields'} } + sort { $info{$a}->{'display_weight'} <=> $info{$b}->{'display_weight'} } + keys %info, + ; + + # yuck. maybe this won't be so bad when virtual fields become real fields + my %vfields; + foreach my $svcdb (grep dbdef->table($_), keys %svc_defs ) { + eval "use FS::$svcdb;"; + my $self = "FS::$svcdb"->new; + $vfields{$svcdb} = {}; + foreach my $field ($self->virtual_fields) { # svc_Common::virtual_fields with a null svcpart returns all of them + my $pvf = $self->pvf($field); + my @list = $pvf->list; + if (scalar @list) { + $svc_defs{$svcdb}->{$field} = { desc => $pvf->label, + type => 'select', + select_list => \@list }; + } else { + $svc_defs{$svcdb}->{$field} = $pvf->label; + } #endif + $vfields{$svcdb}->{$field} = $pvf; + warn "\$vfields{$svcdb}->{$field} = $pvf" + if $DEBUG; + } #next $field + } #next $svcdb + + $svc_defs = \%svc_defs; #cache + +} + +=item svc_tables + +Returns a list of all svc_ tables. + +=cut + +sub svc_tables { + my $class = shift; + my $svc_defs = $class->_svc_defs; + grep { defined( dbdef->table($_) ) } keys %$svc_defs; +} + +=item svc_table_fields TABLE + +Given a table name, returns a hashref of field names. The field names +returned are those with additional (service-definition related) information, +not necessarily all database fields of the table. Pseudo-fields may also +be returned (i.e. svc_acct.usergroup). + +Each value of the hashref is another hashref, which can have one or more of +the following keys: + +=over 4 + +=item label - Description of the field + +=item def_label - Optional description of the field in the context of service definitions + +=item type - Currently "text", "select", "disabled", or "radius_usergroup_selector" + +=item disable_default - This field should not allow a default value in service definitions + +=item disable_fixed - This field should not allow a fixed value in service definitions + +=item disable_inventory - This field should not allow inventory values in service definitions + +=item select_list - If type is "text", this can be a listref of possible values. + +=item select_table - An alternative to select_list, this defines a database table with the possible choices. + +=item select_key - Used with select_table, this is the field name of keys + +=item select_label - Used with select_table, this is the field name of labels + +=back + +=cut + +#maybe this should move and be a class method in svc_Common.pm +sub svc_table_fields { + my($class, $table) = @_; + my $svc_defs = $class->_svc_defs; + my $def = $svc_defs->{$table}; + + foreach ( grep !ref($def->{$_}), keys %$def ) { + + #normalize the shortcut in %info hash + $def->{$_} = { 'label' => $def->{$_} }; + + $def->{$_}{'type'} ||= 'text'; + + } + + $def; +} =back @@ -554,10 +710,7 @@ sub process { } @fields; - } grep defined( dbdef->table($_) ), - qw( svc_acct svc_domain svc_forward svc_www svc_broadband - svc_phone svc_external - ) + } FS::part_svc->svc_tables() ) } ); @@ -651,8 +804,8 @@ sub process_bulk_cust_svc { Delete is unimplemented. -The list of svc_* tables is hardcoded. When svc_acct_pop is renamed, this -should be fixed. +The list of svc_* tables is no longer hardcoded, but svc_acct_pop is skipped +as a special case until it is renamed. all_part_svc_column methods should be documented diff --git a/FS/FS/pkg_svc.pm b/FS/FS/pkg_svc.pm index 065ddbe51..9f3a4a1b7 100644 --- a/FS/FS/pkg_svc.pm +++ b/FS/FS/pkg_svc.pm @@ -82,7 +82,9 @@ returns the error, otherwise returns false. =cut sub replace { - my ( $new, $old ) = ( shift, shift ); + my( $new, $old ) = ( shift, shift ); + + $old = $new->replace_old unless defined($old); return "Can't change pkgpart!" if $old->pkgpart != $new->pkgpart; return "Can't change svcpart!" if $old->svcpart != $new->svcpart; diff --git a/FS/FS/registrar.pm b/FS/FS/registrar.pm new file mode 100644 index 000000000..cf5dc4907 --- /dev/null +++ b/FS/FS/registrar.pm @@ -0,0 +1,119 @@ +package FS::registrar; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::registrar - Object methods for registrar records + +=head1 SYNOPSIS + + use FS::registrar; + + $record = new FS::registrar \%hash; + $record = new FS::registrar { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::registrar object represents a registrar. FS::registrar inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item registrarnum - primary key + +=item registrarname - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new registrar. To add the registrar 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 { 'registrar'; } + +=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 registrar. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('registrarnum') + || $self->ut_text('registrarname') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index 72e7d5c6f..f60a7d945 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -2,7 +2,7 @@ package FS::svc_Common; use strict; use vars qw( @ISA $noexport_hack $DEBUG $me ); -use Carp; +use Carp qw( cluck carp croak ); #specify cluck have to specify them all.. use FS::Record qw( qsearch qsearchs fields dbh ); use FS::cust_main_Mixin; use FS::cust_svc; @@ -36,6 +36,27 @@ inherit from, i.e. FS::svc_acct. FS::svc_Common inherits from FS::Record. =over 4 +=item search_sql_field FIELD STRING + +Class method which returns an SQL fragment to search for STRING in FIELD. + +=cut + +sub search_sql_field { + my( $class, $field, $string ) = @_; + my $table = $class->table; + my $q_string = dbh->quote($string); + "$table.$field = $q_string"; +} + +#fallback for services that don't provide a search... +sub search_sql { + #my( $class, $string ) = @_; + '1 = 0'; #false +} + +=item new + =cut sub new { @@ -114,6 +135,19 @@ sub virtual_fields { return (); } +=item label + +svc_Common provides a fallback label subroutine that just returns the svcnum. + +=cut + +sub label { + my $self = shift; + cluck "warning: ". ref($self). " not loaded or missing label method; ". + "using svcnum"; + $self->svcnum; +} + =item check Checks the validity of fields in this record. @@ -300,35 +334,15 @@ sub delete { local $SIG{TSTP} = 'IGNORE'; local $SIG{PIPE} = 'IGNORE'; - my $svcnum = $self->svcnum; - my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; my $dbh = dbh; - $error = $self->SUPER::delete; - return $error if $error; - - #new-style exports! - unless ( $noexport_hack ) { - foreach my $part_export ( $self->cust_svc->part_svc->part_export ) { - $error = $part_export->export_delete($self); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "exporting to ". $part_export->exporttype. - " (transaction rolled back): $error"; - } - } - } - - $error = $self->return_inventory; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "error returning inventory: $error"; - } - - my $cust_svc = $self->cust_svc; - $error = $cust_svc->delete; + $error = $self->SUPER::delete + || $self->export('delete') + || $self->return_inventory + || $self->cust_svc->delete + ; if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -361,18 +375,7 @@ sub replace { my $dbh = dbh; # We absolutely have to have an old vs. new record to make this work. - if ( !defined($old) ) { - warn "[$me] replace called with no arguments; autoloading old record\n" - if $DEBUG; - my $primary_key = $new->dbdef_table->primary_key; - if ( $primary_key ) { - $old = qsearchs($new->table, { $primary_key => $new->$primary_key() } ) - or croak "can't find ". $new->table. ".$primary_key ". - $new->$primary_key(); - } else { - croak $new->table. " has no primary key; pass old record as argument"; - } - } + $old = $new->replace_old unless defined($old); my $error = $new->set_auto_inventory; if ( $error ) { @@ -678,33 +681,7 @@ Runs export_suspend callbacks. sub suspend { 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; - - #new-style exports! - unless ( $noexport_hack ) { - foreach my $part_export ( $self->cust_svc->part_svc->part_export ) { - my $error = $part_export->export_suspend($self); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "error exporting to ". $part_export->exporttype. - " (transaction rolled back): $error"; - } - } - } - - $dbh->commit or die $dbh->errstr if $oldAutoCommit; - ''; - + $self->export('suspend'); } =item unsuspend @@ -715,6 +692,19 @@ Runs export_unsuspend callbacks. sub unsuspend { my $self = shift; + $self->export('unsuspend'); +} + +=item export HOOK [ EXPORT_ARGS ] + +Runs the provided export hook (i.e. "suspend", "unsuspend") for this service. + +=cut + +sub export { + my( $self, $method ) = ( shift, shift ); + + $method = "export_$method" unless $method =~ /^export_/; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -730,10 +720,11 @@ sub unsuspend { #new-style exports! unless ( $noexport_hack ) { foreach my $part_export ( $self->cust_svc->part_svc->part_export ) { - my $error = $part_export->export_unsuspend($self); + next unless $part_export->can($method); + my $error = $part_export->$method($self, @_); if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "error exporting to ". $part_export->exporttype. + return "error exporting $method event to ". $part_export->exporttype. " (transaction rolled back): $error"; } } @@ -787,6 +778,8 @@ sub clone_kludge_unsuspend { The setfixed method return value. +B method isn't used by insert and replace methods yet. + =head1 SEE ALSO L, L, L, L, schema.html diff --git a/FS/FS/svc_External_Common.pm b/FS/FS/svc_External_Common.pm new file mode 100644 index 000000000..a5805aafd --- /dev/null +++ b/FS/FS/svc_External_Common.pm @@ -0,0 +1,199 @@ +package FS::svc_External_Common; + +use strict; +use vars qw(@ISA); +use FS::svc_Common; + +@ISA = qw( FS::svc_Common ); + +=head1 NAME + +FS::svc_external - Object methods for svc_external records + +=head1 SYNOPSIS + + use FS::svc_external; + + $record = new FS::svc_external \%hash; + $record = new FS::svc_external { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + + $error = $record->suspend; + + $error = $record->unsuspend; + + $error = $record->cancel; + +=head1 DESCRIPTION + +FS::svc_External_Common is intended as a base class for table-specific classes +to inherit from. FS::svc_External_Common is used for services which connect +to externally tracked services via "id" and "table" fields. + +FS::svc_External_Common inherits from FS::svc_Common. + +The following fields are currently supported: + +=over 4 + +=item svcnum - primary key + +=item id - unique number of external record + +=item title - for invoice line items + +=back + +=head1 METHODS + +=over 4 + +=item search_sql + +Provides a default search_sql method which returns an SQL fragment to search +the B field. + +=cut + +sub search_sql { + my($class, $string) = @_; + $class->search_sql_field('title', $string); +} + +=item new HASHREF + +Creates a new external service. To add the external service to the database, +see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I<hash> method. + +=cut + +=item label + +Returns a string identifying this external service in the form "id:title" + +=cut + +sub label { + my $self = shift; + $self->id. ':'. $self->title; +} + +=item insert [ , OPTION => VALUE ... ] + +Adds this external service to the database. If there is an error, returns the +error, otherwise returns false. + +The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be +defined. An FS::cust_svc record will be created and inserted. + +Currently available options are: I<depend_jobnum> + +If I<depend_jobnum> is set (to a scalar jobnum or an array reference of +jobnums), all provisioning jobs will have a dependancy on the supplied +jobnum(s) (they will not run until the specific job(s) complete(s)). + +=cut + +#sub insert { +# my $self = shift; +# my $error; +# +# $error = $self->SUPER::insert(@_); +# return $error if $error; +# +# ''; +#} + +=item delete + +Delete this record from the database. + +=cut + +#sub delete { +# my $self = shift; +# my $error; +# +# $error = $self->SUPER::delete; +# return $error if $error; +# +# ''; +#} + + +=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 + +#sub replace { +# my ( $new, $old ) = ( shift, shift ); +# my $error; +# +# $error = $new->SUPER::replace($old); +# return $error if $error; +# +# ''; +#} + +=item suspend + +Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>). + +=item unsuspend + +Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>). + +=item cancel + +Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>). + +=item check + +Checks all fields to make sure this is a valid external service. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +sub check { + my $self = shift; + + my $x = $self->setfixed; + return $x unless ref($x); + my $part_svc = $x; + + my $error = + $self->ut_numbern('svcnum') + || $self->ut_numbern('id') + || $self->ut_textn('title') + ; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, +L<FS::cust_pkg>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/svc_Parent_Mixin.pm b/FS/FS/svc_Parent_Mixin.pm new file mode 100644 index 000000000..4501bafc8 --- /dev/null +++ b/FS/FS/svc_Parent_Mixin.pm @@ -0,0 +1,103 @@ +package FS::svc_Parent_Mixin; + +use strict; +use NEXT; +use FS::Record qw(qsearch qsearchs); +use FS::cust_svc; + +=head1 NAME + +FS::svc_Parent_Mixin - Mixin class for svc_ classes with a parent_svcnum field + +=head1 SYNOPSIS + +package FS::svc_table; +use vars qw(@ISA); +@ISA = qw( FS::svc_Parent_Mixin FS::svc_Common ); + +=head1 DESCRIPTION + +This is a mixin class for svc_ classes that contain a parent_svcnum field. + +=cut + +=head1 METHODS + +=over 4 + +=item parent_cust_svc + +Returns the parent FS::cust_svc object. + +=cut + +sub parent_cust_svc { + my $self = shift; + qsearchs('cust_svc', { 'svcnum' => $self->parent_svcnum } ); +} + +=item parent_svc_x + +Returns the corresponding parent FS::svc_ object. + +=cut + +sub parent_svc_x { + my $self = shift; + $self->parent_cust_svc->svc_x; +} + +=item children_cust_svc + +Returns a list of any child FS::cust_svc objects. + +Note: This is not recursive; it only returns direct children. + +=cut + +sub children_cust_svc { + my $self = shift; + qsearch('cust_svc', { 'parent_svcnum' => $self->svcnum } ); +} + +=item children_svc_x + +Returns the corresponding list of child FS::svc_ objects. + +=cut + +sub children_svc_x { + my $self = shift; + map { $_->svc_x } $self->children_cust_svc; +} + +=item check + +This class provides a check subroutine which takes care of checking the +parent_svcnum field. The svc_ class which uses it will call SUPER::check at +the end of its own checks, and this class will call NEXT::check to pass +the check "up the chain" (see L<NEXT>). + +=cut + +sub check { + my $self = shift; + + $self->ut_foreign_keyn('parent_svcnum', 'cust_svc', 'svcnum') + || $self->NEXT::check; + +} + +=back + +=head1 BUGS + +Do we need a recursive child finder for multi-layered children? + +=head1 SEE ALSO + +L<FS::svc_Common>, L<FS::Record> + +=cut + +1; diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 8c1c350fa..7cbe63f6c 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -210,6 +210,77 @@ Creates a new account. To add the account to the database, see L<"insert">. =cut +sub table_info { + { + 'name' => 'Account', + 'longname_plural' => 'Access accounts and mailboxes', + 'sorts' => [ 'username', 'uid', ], + 'display_weight' => 10, + 'cancel_weight' => 50, + 'fields' => { + 'dir' => 'Home directory', + 'uid' => { + label => 'UID', + def_label => 'UID (set to fixed and blank for no UIDs)', + type => 'text', + }, + 'slipip' => 'IP address', + # 'popnum' => qq!<A HREF="$p/browse/svc_acct_pop.cgi/">POP number</A>!, + 'popnum' => { + label => 'Access number', + type => 'select', + select_table => 'svc_acct_pop', + select_key => 'popnum', + select_label => 'city', + }, + 'username' => { + label => 'Username', + type => 'text', + disable_default => 1, + disable_fixed => 1, + }, + 'quota' => { + label => 'Quota', + type => 'text', + disable_inventory => 1, + }, + '_password' => 'Password', + 'gid' => { + label => 'GID', + def_label => 'GID (when blank, defaults to UID)', + type => 'text', + }, + 'shell' => { + #desc =>'Shell (all service definitions should have a default or fixed shell that is present in the <b>shells</b> configuration file, set to blank for no shell tracking)', + label => 'Shell', + def_label=> 'Shell (set to blank for no shell tracking)', + type =>'select', + select_list => [ $conf->config('shells') ], + disable_inventory => 1, + }, + 'finger' => 'Real name (GECOS)', + 'domsvc' => { + label => 'Domain', + def_label => 'svcnum from svc_domain', + type => 'select', + select_table => 'svc_domain', + select_key => 'svcnum', + select_label => 'domain', + disable_inventory => 1, + }, + 'usergroup' => { + label => 'RADIUS groups', + type => 'radius_usergroup_selector', + disable_inventory => 1, + }, + 'seconds' => { label => 'Seconds', + type => 'text', + disable_inventory => 1, + }, + }, + }; +} + sub table { 'svc_acct'; } sub _fieldhandlers { @@ -228,6 +299,52 @@ sub _fieldhandlers { }; } +=item search_sql STRING + +Class method which returns an SQL fragment to search for the given string. + +=cut + +sub search_sql { + my( $class, $string ) = @_; + if ( $string =~ /^([^@]+)@([^@]+)$/ ) { + my( $username, $domain ) = ( $1, $2 ); + my $q_username = dbh->quote($username); + my @svc_domain = qsearch('svc_domain', { 'domain' => $domain } ); + if ( @svc_domain ) { + "svc_acct.username = $q_username AND ( ". + join( ' OR ', map { "svc_acct.domsvc = ". $_->svcnum; } @svc_domain ). + " )"; + } else { + '1 = 0'; #false + } + } elsif ( $string =~ /^(\d{1,3}\.){3}\d{1,3}$/ ) { + ' ( '. + $class->search_sql_field('slipip', $string ). + ' OR '. + $class->search_sql_field('username', $string ). + ' ) '; + } else { + $class->search_sql_field('username', $string); + } +} + +=item label [ END_TIMESTAMP [ START_TIMESTAMP ] ] + +Returns the "username@domain" string for this account. + +END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with +history records. + +=cut + +sub label { + my $self = shift; + $self->email(@_); +} + +=cut + =item insert [ , OPTION => VALUE ... ] Adds this account to the database. If there is an error, returns the error, @@ -1180,10 +1297,13 @@ sub forget_snapshot { } -=item domain +=item domain [ END_TIMESTAMP [ START_TIMESTAMP ] ] Returns the domain associated with this account. +END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with +history records. + =cut sub domain { @@ -1201,6 +1321,8 @@ L<FS::svc_domain>). =cut +# FS::h_svc_acct has a history-aware svc_domain override + sub svc_domain { my $self = shift; $self->{'_domsvc'} @@ -1216,10 +1338,13 @@ Returns the FS::cust_svc record for this account (see L<FS::cust_svc>). #inherited from svc_Common -=item email +=item email [ END_TIMESTAMP [ START_TIMESTAMP ] ] Returns an email address associated with the account. +END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with +history records. + =cut sub email { diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index 5c9fe5e12..b047c9a73 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -85,8 +85,50 @@ points to. You can ask the object for a copy with the I<hash> method. =cut +sub table_info { + { + 'name' => 'Broadband', + 'name_plural' => 'Broadband services', + 'longname_plural' => 'Fixed (username-less) broadband services', + 'display_weight' => 50, + 'cancel_weight' => 70, + 'fields' => { + 'speed_down' => 'Maximum download speed for this service in Kbps. 0 denotes unlimited.', + 'speed_up' => 'Maximum upload speed for this service in Kbps. 0 denotes unlimited.', + 'ip_addr' => 'IP address. Leave blank for automatic assignment.', + 'blocknum' => 'Address block.', + }, + }; +} + sub table { 'svc_broadband'; } +=item search_sql STRING + +Class method which returns an SQL fragment to search for the given string. + +=cut + +sub search_sql { + my( $class, $string ) = @_; + if ( $string =~ /^(\d{1,3}\.){3}\d{1,3}$/ ) { + $class->search_sql_field('ip_addr', $string ); + } else { + '1 = 0'; #false + } +} + +=item label + +Returns the IP address. + +=cut + +sub label { + my $self = shift; + $self->ip_addr; +} + =item insert [ , OPTION => VALUE ... ] Adds this record to the database. If there is an error, returns the error, diff --git a/FS/FS/svc_domain.pm b/FS/FS/svc_domain.pm index bdaf79b2f..157f9e0ca 100644 --- a/FS/FS/svc_domain.pm +++ b/FS/FS/svc_domain.pm @@ -11,6 +11,7 @@ use Date::Format; use FS::Record qw(fields qsearch qsearchs dbh); use FS::Conf; use FS::svc_Common; +use FS::svc_Parent_Mixin; use FS::cust_svc; use FS::svc_acct; use FS::cust_pkg; @@ -18,7 +19,7 @@ use FS::cust_main; use FS::domain_record; use FS::queue; -@ISA = qw( FS::svc_Common ); +@ISA = qw( FS::svc_Parent_Mixin FS::svc_Common ); #ask FS::UID to run this stuff for us later $FS::UID::callback{'FS::domain'} = sub { @@ -72,6 +73,20 @@ FS::svc_Common. The following fields are currently supported: =item catchall - optional svcnum of an svc_acct record, designating an email catchall account. +=item suffix - + +=item parent_svcnum - + +=item registrarnum - Registrar (see L<FS::registrar>) + +=item registrarkey - Registrar key or password for this domain + +=item setup_date - UNIX timestamp + +=item renewal_interval - Number of days before expiration date to start renewal + +=item expiration_date - UNIX timestamp + =back =head1 METHODS @@ -84,8 +99,37 @@ Creates a new domain. To add the domain to the database, see L<"insert">. =cut +sub table_info { + { + 'name' => 'Domain', + 'sorts' => 'domain', + 'display_weight' => 20, + 'cancel_weight' => 60, + 'fields' => { + 'domain' => 'Domain', + }, + }; +} + sub table { 'svc_domain'; } +sub search_sql { + my($class, $string) = @_; + $class->search_sql_field('domain', $string); +} + + +=item label + +Returns the domain. + +=cut + +sub label { + my $self = shift; + $self->domain; +} + =item insert [ , OPTION => VALUE ... ] Adds this domain to the database. If there is an error, returns the error, @@ -141,15 +185,6 @@ sub insert { return "Domain in use (here)" if qsearchs( 'svc_domain', { 'domain' => $self->domain } ); - my $whois = $self->whois; - if ( $self->action eq "N" && ! $whois_hack && $whois ) { - $dbh->rollback if $oldAutoCommit; - return "Domain in use (see whois)"; - } - if ( $self->action eq "M" && ! $whois ) { - $dbh->rollback if $oldAutoCommit; - return "Domain not found (see whois)"; - } $error = $self->SUPER::insert(@_); if ( $error ) { @@ -157,8 +192,6 @@ sub insert { return $error; } - $self->submit_internic unless $whois_hack; - if ( $soamachine ) { my $soa = new FS::domain_record { 'svcnum' => $self->svcnum, @@ -257,6 +290,9 @@ returns the error, otherwise returns false. sub replace { my ( $new, $old ) = ( shift, shift ); + # We absolutely have to have an old vs. new record to make this work. + $old = $new->replace_old unless defined($old); + return "Can't change domain - reorder." if $old->getfield('domain') ne $new->getfield('domain'); @@ -317,45 +353,32 @@ sub check { my($recref) = $self->hashref; - unless ( $whois_hack ) { - unless ( $self->email ) { #find out an email address - my @svc_acct; - foreach ( qsearch( 'cust_svc', { 'pkgnum' => $pkgnum } ) ) { - my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $_->svcnum } ); - push @svc_acct, $svc_acct if $svc_acct; - } - - if ( scalar(@svc_acct) == 0 ) { - return "Must order an account in package ". $pkgnum. " first"; - } elsif ( scalar(@svc_acct) > 1 ) { - return "More than one account in package ". $pkgnum. ": specify admin contact email"; - } else { - $self->email($svc_acct[0]->email ); - } - } - } - #if ( $recref->{domain} =~ /^([\w\-\.]{1,22})\.(com|net|org|edu)$/ ) { - if ( $recref->{domain} =~ /^([\w\-]{1,63})\.(com|net|org|edu)$/ ) { + if ( $recref->{domain} =~ /^([\w\-]{1,63})\.(com|net|org|edu|tv|info|biz)$/ ) { $recref->{domain} = "$1.$2"; + $recref->{suffix} ||= $2; # hmmmmmmmm. - } elsif ( $whois_hack && $recref->{domain} =~ /^([\w\-\.]+)$/ ) { - $recref->{domain} = $1; + } elsif ( $whois_hack && $recref->{domain} =~ /^([\w\-\.]+)\.(\w+)$/ ) { + $recref->{domain} = "$1.$2"; + # need to match a list of suffixes - no guarantee they're top-level.. } else { return "Illegal domain ". $recref->{domain}. " (or unknown registry - try \$whois_hack)"; } - $recref->{action} =~ /^(M|N)$/ - or return "Illegal action: ". $recref->{action}; - $recref->{action} = $1; if ( $recref->{catchall} ne '' ) { my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $recref->{catchall} } ); return "Unknown catchall" unless $svc_acct; } - $self->ut_textn('purpose') + $self->ut_alphan('suffix') + or $self->ut_foreign_keyn('registrarnum', 'registrar', 'registrarnum') + or $self->ut_textn('registrarkey') + or $self->ut_numbern('setup_date') + or $self->ut_numbern('renewal_interval') + or $self->ut_numbern('expiration_date') + or $self->ut_textn('purpose') or $self->SUPER::check; } @@ -402,7 +425,7 @@ sub catchall_svc_acct { sub whois { #$whois_hack or new Net::Whois::Domain $_[0]->domain; - $whois_hack or die "whois_hack not set...\n"; + #$whois_hack or die "whois_hack not set...\n"; } =item _whois diff --git a/FS/FS/svc_external.pm b/FS/FS/svc_external.pm index 14eab7e03..5aaee4872 100644 --- a/FS/FS/svc_external.pm +++ b/FS/FS/svc_external.pm @@ -1,16 +1,11 @@ package FS::svc_external; use strict; -use vars qw(@ISA); # $conf -use FS::UID; -#use FS::Record qw( qsearch qsearchs dbh); -use FS::svc_Common; +use vars qw(@ISA); +use FS::Conf; +use FS::svc_External_Common; -@ISA = qw( FS::svc_Common ); - -#FS::UID::install_callback( sub { -# $conf = new FS::Conf; -#}; +@ISA = qw( FS::svc_External_Common ); =head1 NAME @@ -39,9 +34,9 @@ FS::svc_external - Object methods for svc_external records =head1 DESCRIPTION -An FS::svc_external object represents a externally tracked service. -FS::svc_external inherits from FS::svc_Common. The following fields are -currently supported: +An FS::svc_external object represents a generic externally tracked service. +FS::svc_external inherits from FS::svc_External_Common (and FS::svc_Common). +The following fields are currently supported: =over 4 @@ -67,8 +62,31 @@ points to. You can ask the object for a copy with the I<hash> method. =cut +sub table_info { + { + 'name' => 'External service', + 'sorts' => 'id', + 'display_weight' => 90, + 'cancel_weight' => 10, + 'fields' => { + }, + }; +} + sub table { 'svc_external'; } +# oh! this should be moved to svc_artera_turbo or something now +sub label { + my $self = shift; + my $conf = new FS::Conf; + if ( $conf->config('svc_external-display_type') eq 'artera_turbo' ) { + sprintf('%010d', $self->id). '-'. + substr('0000000000'.uc($self->title), -10); + } else { + $self->SUPER::label; + } +} + =item insert [ , OPTION => VALUE ... ] Adds this external service to the database. If there is an error, returns the @@ -149,21 +167,15 @@ and replace methods. =cut -sub check { - my $self = shift; - - my $x = $self->setfixed; - return $x unless ref($x); - my $part_svc = $x; - - my $error = - $self->ut_numbern('svcnum') - || $self->ut_numbern('id') - || $self->ut_textn('title') - ; - - $self->SUPER::check; -} +#sub check { +# my $self = shift; +# my $error; +# +# $error = $self->SUPER::delete; +# return $error if $error; +# +# ''; +#} =back @@ -171,8 +183,8 @@ sub check { =head1 SEE ALSO -L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, -L<FS::cust_pkg>, schema.html from the base documentation. +L<FS::svc_External_Common>, L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, +L<FS::part_svc>, L<FS::cust_pkg>, schema.html from the base documentation. =cut diff --git a/FS/FS/svc_forward.pm b/FS/FS/svc_forward.pm index ab24d3277..91e251fa0 100644 --- a/FS/FS/svc_forward.pm +++ b/FS/FS/svc_forward.pm @@ -66,8 +66,67 @@ database, see L<"insert">. =cut + +sub table_info { + { + 'name' => 'Forward', + 'name_plural' => 'Mail forwards', + 'display_weight' => 30, + 'cancel_weight' => 30, + 'fields' => { + 'srcsvc' => 'service from which mail is to be forwarded', + 'dstsvc' => 'service to which mail is to be forwarded', + 'dst' => 'someone@another.domain.com to use when dstsvc is 0', + }, + }; +} + sub table { 'svc_forward'; } +=item search_sql STRING + +Class method which returns an SQL fragment to search for the given string. + +=cut + +sub search_sql { + my( $class, $string ) = @_; + $class->search_sql_field('src', $string); +} + +=item label [ END_TIMESTAMP [ START_TIMESTAMP ] ] + +Returns a text string representing this forward. + +END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with +history records. + +=cut + +sub label { + my $self = shift; + my $tag = ''; + + if ( $self->srcsvc ) { + my $svc_acct = $self->srcsvc_acct(@_); + $tag = $svc_acct->email(@_); + } else { + $tag = $self->src; + } + + $tag .= ' -> '; + + if ( $self->dstsvc ) { + my $svc_acct = $self->dstsvc_acct(@_); + $tag .= $svc_acct->email(@_); + } else { + $tag .= $self->dst; + } + + $tag; +} + + =item insert [ , OPTION => VALUE ... ] Adds this mail forwarding alias to the database. If there is an error, returns diff --git a/FS/FS/svc_phone.pm b/FS/FS/svc_phone.pm index fca33690d..8e39b9f10 100644 --- a/FS/FS/svc_phone.pm +++ b/FS/FS/svc_phone.pm @@ -63,9 +63,51 @@ points to. You can ask the object for a copy with the I<hash> method. =cut # the new method can be inherited from FS::Record, if a table method is defined +# +sub table_info { + { + 'name' => 'Phone number', + 'sorts' => 'phonenum', + 'display_weight' => 60, + 'cancel_weight' => 80, + 'fields' => { + 'countrycode' => { label => 'Country code', + type => 'text', + disable_inventory => 1, + }, + 'phonenum' => 'Phone number', + 'pin' => { label => 'Personal Identification Number', + type => 'text', + disable_inventory => 1, + }, + }, + }; +} sub table { 'svc_phone'; } +=item search_sql STRING + +Class method which returns an SQL fragment to search for the given string. + +=cut + +sub search_sql { + my( $class, $string ) = @_; + $class->search_sql_field('phonenum', $string ); +} + +=item label + +Returns the phone number. + +=cut + +sub label { + my $self = shift; + $self->phonenum; #XXX format it better +} + =item insert Adds this record to the database. If there is an error, returns the error, diff --git a/FS/FS/svc_www.pm b/FS/FS/svc_www.pm index 7c7032f4c..066719bbe 100644 --- a/FS/FS/svc_www.pm +++ b/FS/FS/svc_www.pm @@ -72,8 +72,33 @@ points to. You can ask the object for a copy with the I<hash> method. =cut +sub table_info { + { + 'name' => 'Hosting', + 'name_plural' => 'Virtual hosting services', + 'display_weight' => 40, + 'cancel_weight' => 20, + 'fields' => { + }, + }; +}; + sub table { 'svc_www'; } +=item label [ END_TIMESTAMP [ START_TIMESTAMP ] ] + +Returns the zone name for this virtual host. + +END_TIMESTAMP and START_TIMESTAMP can optionally be passed when dealing with +history records. + +=cut + +sub label { + my $self = shift; + $self->domain_record(@_)->zone; +} + =item insert [ , OPTION => VALUE ... ] Adds this record to the database. If there is an error, returns the error, diff --git a/FS/MANIFEST b/FS/MANIFEST index 906cc9cdb..c5c40c67d 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -355,6 +355,12 @@ FS/cust_bill_pay_pkg.pm t/cust_bill_pay_pkg.t FS/cust_credit_bill_pkg.pm t/cust_credit_bill_pkg.t +FS/registrar.pm +t/registrar.t +FS/svc_External_Common.pm +t/svc_External_Common.t +FS/svc_Parent_Mixin.pm +t/svc_Parent_Mixin.t FS/cust_main_note.pm t/cust_main_note.t FS/cust_pkg_reason.pm diff --git a/FS/t/registrar.t b/FS/t/registrar.t new file mode 100644 index 000000000..a6ba13437 --- /dev/null +++ b/FS/t/registrar.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::registrar; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/svc_External_Common.t b/FS/t/svc_External_Common.t new file mode 100644 index 000000000..a0b2ea2fd --- /dev/null +++ b/FS/t/svc_External_Common.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::svc_External_Common; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/svc_Parent_Mixin.t b/FS/t/svc_Parent_Mixin.t new file mode 100644 index 000000000..ed9923fc0 --- /dev/null +++ b/FS/t/svc_Parent_Mixin.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::svc_Parent_Mixin; +$loaded=1; +print "ok 1\n"; diff --git a/eg/table_template-svc.pm b/eg/table_template-svc.pm index 7f7ef4b68..47dcbe6e4 100644 --- a/eg/table_template-svc.pm +++ b/eg/table_template-svc.pm @@ -59,6 +59,60 @@ points to. You can ask the object for a copy with the I<hash> method. sub table { 'table_name'; } +sub table_info { + { + 'name' => 'Example', + 'name_plural' => 'Example services', #optional, + 'longname_plural' => 'Example services', #optional + 'sorts' => 'svcnum', # optional sort field (or arrayref of sort fields, main first) + 'display_weight' => 100, + 'cancel_weight' => 100, + 'fields' => { + 'field' => 'Description', + 'another_field' => { + 'label' => 'Description', + 'def_label' => 'Description for service definitions', + 'type' => 'text', + 'disable_default' => 1, #disable switches + 'disable_fixed' => 1, # + 'disable_inventory' => 1, # + }, + 'foreign_key' => { + 'label' => 'Description', + 'def_label' => 'Description for service defs', + 'type' => 'select', + 'select_table' => 'foreign_table', + 'select_key' => 'key_field_in_table', + 'select_label' => 'label_field_in_table', + }, + + }, + }; +} + +=item search_sql STRING + +Class method which returns an SQL fragment to search for the given string. + +=cut + +#or something more complicated if necessary +sub search_sql { + my($class, $string) = @_; + $class->search_sql_field('search_field', $string); +} + +=item label + +Returns a meaningful identifier for this example + +=cut + +sub label { + my $self = shift; + $self->label_field; #or something more complicated if necessary +} + =item insert Adds this record to the database. If there is an error, returns the error, diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi index 795393574..6198a1aec 100755 --- a/httemplate/browse/part_svc.cgi +++ b/httemplate/browse/part_svc.cgi @@ -72,21 +72,33 @@ function part_export_areyousure(href) { %> % $cgi->param('showdisabled', ( 1 ^ $cgi->param('showdisabled') ) ); -<% table() %> +<% include('/elements/table-grid.html') %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; + <TR> - <TH><A HREF="<% do { $cgi->param('orderby', 'svcpart'); $cgi->self_url } %>">#</A></TH> -% if ( $cgi->param('showdisabled') ) { - <TH>Status</TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><A HREF="<% do { $cgi->param('orderby', 'svcpart'); $cgi->self_url } %>">#</A></TH> + +% if ( $cgi->param('showdisabled') ) { + <TH CLASS="grid" BGCOLOR="#cccccc">Status</TH> % } - <TH><A HREF="<% do { $cgi->param('orderby', 'svc'); $cgi->self_url; } %>">Service</A></TH> - <TH>Table</TH> - <TH><A HREF="<% do { $cgi->param('orderby', 'active'); $cgi->self_url; } %>"><FONT SIZE=-1>Customer<BR>Services</FONT></A></TH> - <TH>Export</TH> - <TH>Field</TH> - <TH COLSPAN=2>Modifier</TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><A HREF="<% do { $cgi->param('orderby', 'svc'); $cgi->self_url; } %>">Service</A></TH> + + <TH CLASS="grid" BGCOLOR="#cccccc">Table</TH> + + <TH CLASS="grid" BGCOLOR="#cccccc"><A HREF="<% do { $cgi->param('orderby', 'active'); $cgi->self_url; } %>"><FONT SIZE=-1>Customer<BR>Services</FONT></A></TH> + + <TH CLASS="grid" BGCOLOR="#cccccc">Export</TH> + + <TH CLASS="grid" BGCOLOR="#cccccc">Field</TH> + + <TH COLSPAN=2 CLASS="grid" BGCOLOR="#cccccc">Modifier</TH> + </TR> + % foreach my $part_svc ( @part_svc ) { % my $svcdb = $part_svc->svcdb; % my $svc_x = "FS::$svcdb"->new( { svcpart => $part_svc->svcpart } ); @@ -99,14 +111,21 @@ function part_export_areyousure(href) { % my $rowspan = scalar(@fields) || 1; % my $url = "${p}edit/part_svc.cgi?". $part_svc->svcpart; % +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } <TR> - <TD ROWSPAN=<% $rowspan %>><A HREF="<% $url %>"> - <% $part_svc->svcpart %></A></TD> -% if ( $cgi->param('showdisabled') ) { - <TD ROWSPAN=<% $rowspan %>> + <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <A HREF="<% $url %>"><% $part_svc->svcpart %></A> + </TD> + +% if ( $cgi->param('showdisabled') ) { + <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"> <% $part_svc->disabled ? '<FONT COLOR="#FF0000"><B>Disabled</B></FONT>' : '<FONT COLOR="#00CC00"><B>Enabled</B></FONT>' @@ -114,19 +133,23 @@ function part_export_areyousure(href) { </TD> % } - <TD ROWSPAN=<% $rowspan %>><A HREF="<% $url %>"> + <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $url %>"> <% $part_svc->svc %></A></TD> - <TD ROWSPAN=<% $rowspan %>> + + <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"> <% $svcdb %></TD> - <TD ROWSPAN=<% $rowspan %>> - <FONT COLOR="#00CC00"><B><% $num_active_cust_svc{$part_svc->svcpart} %></B></FONT> <A HREF="<%$p%>search/<% $svcdb %>.cgi?svcpart=<% $part_svc->svcpart %>">active</A> -% if ( $num_active_cust_svc{$part_svc->svcpart} ) { + <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <FONT COLOR="#00CC00"><B><% $num_active_cust_svc{$part_svc->svcpart} %></B></FONT> <% $num_active_cust_svc{$part_svc->svcpart} ? FS::UI::Web::svc_url( 'ahref' => 1, 'm' => $m, 'action' => 'search', 'part_svc' => $part_svc, 'query' => "svcpart=". $part_svc->svcpart ) : '<A NAME="zero">' %>active</A> + +% if ( $num_active_cust_svc{$part_svc->svcpart} ) { <BR><FONT SIZE="-1">[ <A HREF="<%$p%>edit/bulk-cust_svc.html?svcpart=<% $part_svc->svcpart %>">change</A> ]</FONT> % } </TD> - <TD ROWSPAN=<% $rowspan %>><% itable() %> + + <TD ROWSPAN=<% $rowspan %> CLASS="inv" BGCOLOR="<% $bgcolor %>"> + <TABLE CLASS="inv"> % %# my @part_export = %map { qsearchs('part_export', { exportnum => $_->exportnum } ) } qsearch('export_svc', { svcpart => $part_svc->svcpart } ) ; @@ -136,21 +159,30 @@ function part_export_areyousure(href) { % ) { % - <TR> - <TD><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>"><% $part_export->exportnum %>: <% $part_export->exporttype %> to <% $part_export->machine %></A></TD></TR> + <TR> + <TD><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>"><% $part_export->exportnum %>: <% $part_export->exporttype %> to <% $part_export->machine %></A></TD> + </TR> % } - </TABLE></TD> -% my($n1)=''; + </TABLE> + </TD> + +% unless ( @fields ) { +% for ( 1..3 ) { + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"</TD> +% } +% } +% +% my($n1)=''; % foreach my $field ( @fields ) { % my $flag = $part_svc->part_svc_column($field)->columnflag; % <% $n1 %> - <TD><% $field %></TD> - <TD><% $flag{$flag} %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $field %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $flag{$flag} %></TD> - <TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> % my $value = $part_svc->part_svc_column($field)->columnvalue; % if ( $flag =~ /^[MA]$/ ) { % $inventory_class{$value} diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index c2ea22f27..17c5ad3eb 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -53,6 +53,9 @@ % # # ... % # "html_string"; % # }, +% # +% # # overrides default popurl(1)."process/$table.html" +% # 'post_url' => popurl(1).'process/something', % % my(%opt) = @_; % @@ -77,9 +80,14 @@ % % } elsif ( $cgi->keywords || $cgi->param($pkey) ) { #editing % -% my( $query ) = $cgi->keywords; -% $query = $cgi->param($pkey) unless $query; -% $query =~ /^(\d+)$/; +% my $value; +% if ( $cgi->param($pkey) ) { +% $value = $cgi->param($pkey) +% } else { +% my( $query ) = $cgi->keywords; +% $value = $query; +% } +% $value =~ /^(\d+)$/ or die "unparsable $pkey"; % $object = qsearchs( $table, { $pkey => $1 } ); % warn "$table $pkey => $1" % if $opt{'debug'}; @@ -129,8 +137,10 @@ <BR><BR> % } +% my $url = $opt{'post_url'} || popurl(1)."process/$table.html"; -<FORM ACTION="<% popurl(1) %>process/<% $table %>.html" METHOD=POST> +<FORM ACTION="<% $url %>" METHOD=POST> +<INPUT TYPE="hidden" NAME="svcdb" VALUE="<% $table %>"> <INPUT TYPE="hidden" NAME="<% $pkey %>" VALUE="<% $object->$pkey() %>"> <% ( $opt{labels} && exists $opt{labels}->{$pkey} ) ? $opt{labels}->{$pkey} diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html index da59cc9ed..1fd66c251 100644 --- a/httemplate/edit/elements/svc_Common.html +++ b/httemplate/edit/elements/svc_Common.html @@ -1,19 +1,21 @@ % -% % my %opt = @_; % % #my( $svcnum, $pkgnum, $svcpart, $part_svc ); % my( $pkgnum, $svcpart, $part_svc ); % % #get & untaint pkgnum & svcpart -% my($query) = $cgi->keywords; #they're not proper cgi params -% if ( $query =~ /^pkgnum(\d+)-svcpart(\d+)$/ ) { -% $pkgnum = $1; -% $svcpart = $2; -% $cgi->delete_all(); #so the standard edit.html treats this correctly as new +% if ( ! $cgi->param('error') +% && $cgi->param('pkgnum') && $cgi->param('svcpart') +% ) +% { +% $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum'; +% $pkgnum = $1; +% $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart'; +% $svcpart = $1; +% $cgi->delete_all(); #so edit.html treats this correctly as new?? % } % -% <% include( 'edit.html', 'menubar' => [], diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi index cc9145f45..ba152db9c 100755 --- a/httemplate/edit/part_svc.cgi +++ b/httemplate/edit/part_svc.cgi @@ -55,159 +55,37 @@ For the selected table, you can give fields default or fixed (unchangable) values, or select an inventory class to manually or automatically fill in that field. <BR><BR> -% -% -%#these might belong somewhere else for other user interfaces -%#pry need to eventually create stuff that's shared amount UIs -%my $conf = new FS::Conf; -%my %defs = ( -% -% 'svc_acct' => { -% 'dir' => 'Home directory', -% 'uid' => 'UID (set to fixed and blank for no UIDs)', -% 'slipip' => 'IP address', -%# 'popnum' => qq!<A HREF="$p/browse/svc_acct_pop.cgi/">POP number</A>!, -% 'popnum' => { -% desc => 'Access number', -% type => 'select', -% select_table => 'svc_acct_pop', -% select_key => 'popnum', -% select_label => 'city', -% disable_select => 1, -% }, -% 'username' => { -% desc => 'Username', -% type => 'text', -% disable_default => 1, -% disable_fixed => 1, -% disable_select => 1, -% }, -% 'quota' => { -% desc => '', -% type => 'text', -% disable_inventory => 1, -% disable_select => 1, -% }, -% '_password' => 'Password', -% 'gid' => 'GID (when blank, defaults to UID)', -% 'shell' => { -% #desc =>'Shell (all service definitions should have a default or fixed shell that is present in the <b>shells</b> configuration file, set to blank for no shell tracking)', -% desc =>'Shell ( set to blank for no shell tracking)', -% type =>'select', -% select_list => [ $conf->config('shells') ], -% disable_inventory => 1, -% disable_select => 1, -% }, -% 'finger' => 'Real name (GECOS)', -% 'domsvc' => { -% desc =>'svcnum from svc_domain', -% type =>'select', -% select_table => 'svc_domain', -% select_key => 'svcnum', -% select_label => 'domain', -% disable_inventory => 1, -% disable_select => 1, -% }, -% 'usergroup' => { -% desc =>'RADIUS groups', -% type =>'radius_usergroup_selector', -% disable_select => 1, -% disable_inventory => 1, -% }, -% 'seconds' => { desc => '', -% type => 'text', -% disable_inventory => 1, -% disable_select => 1, -% }, -% }, -% -% 'svc_domain' => { -% 'domain' => 'Domain', -% }, -% -% 'svc_forward' => { -% 'srcsvc' => 'service from which mail is to be forwarded', -% 'dstsvc' => 'service to which mail is to be forwarded', -% 'dst' => 'someone@another.domain.com to use when dstsvc is 0', -% }, -% -%# 'svc_charge' => { -%# 'amount' => 'amount', -%# }, -%# 'svc_wo' => { -%# 'worker' => 'Worker', -%# '_date' => 'Date', -%# }, -% -% 'svc_www' => { -% #'recnum' => '', -% #'usersvc' => '', -% }, -% -% 'svc_broadband' => { -% 'speed_down' => 'Maximum download speed for this service in Kbps. 0 denotes unlimited.', -% 'speed_up' => 'Maximum upload speed for this service in Kbps. 0 denotes unlimited.', -% 'ip_addr' => 'IP address. Leave blank for automatic assignment.', -% 'blocknum' => 'Address block.', -% }, -% -% 'svc_phone' => { -% 'countrycode' => { desc => 'Country code', -% type => 'text', -% disable_inventory => 1, -% disable_select => 1, -% }, -% 'phonenum' => 'Phone number', -% 'pin' => { desc => 'Personal Identification Number', -% type => 'text', -% disable_inventory => 1, -% disable_select => 1, -% }, -% }, -% -% 'svc_external' => { -% #'id' => '', -% #'title' => '', -% }, -% -%); -% -% my %vfields; -% foreach my $svcdb (grep dbdef->table($_), keys %defs ) { -% my $self = "FS::$svcdb"->new; -% $vfields{$svcdb} = {}; -% foreach my $field ($self->virtual_fields) { # svc_Common::virtual_fields with a null svcpart returns all of them -% my $pvf = $self->pvf($field); -% my @list = $pvf->list; -% if (scalar @list) { -% $defs{$svcdb}->{$field} = { desc => $pvf->label, -% type => 'select', -% select_list => \@list }; -% } else { -% $defs{$svcdb}->{$field} = $pvf->label; -% } #endif -% $vfields{$svcdb}->{$field} = $pvf; -% warn "\$vfields{$svcdb}->{$field} = $pvf"; -% } #next $field -% } #next $svcdb + +% #YUCK. false laziness w/part_svc.pm. go away virtual fields, please +% my %vfields; +% foreach my $svcdb ( FS::part_svc->svc_tables() ) { +% eval "use FS::$svcdb;"; +% my $self = "FS::$svcdb"->new; +% $vfields{$svcdb} = {}; +% foreach my $field ($self->virtual_fields) { # svc_Common::virtual_fields with a null svcpart returns all of them +% my $pvf = $self->pvf($field); +% $vfields{$svcdb}->{$field} = $pvf; +% #warn "\$vfields{$svcdb}->{$field} = $pvf"; +% } #next $field +% } #next $svcdb % % #code duplication w/ edit/part_svc.cgi, should move this hash to part_svc.pm % # and generalize the subs % # condition sub is tested to see whether to disable display of this choice % # params: ( $def, $layer, $field ) (see SUB below) % my $inv_sub = sub { -% ref($_[0]) && ( $_[0]->{disable_inventory} -% || $_[0]->{'type'} ne 'text' ) -% }; +% $_[0]->{disable_inventory} +% || $_[0]->{'type'} ne 'text' +% }; % tie my %flag, 'Tie::IxHash', % '' => { 'desc' => 'No default', }, % 'D' => { 'desc' => 'Default', % 'condition' => -% sub { ref($_[0]) && $_[0]->{disable_default} }, +% sub { $_[0]->{disable_default} }, % }, % 'F' => { 'desc' => 'Fixed (unchangeable)', % 'condition' => -% sub { ref($_[0]) && $_[0]->{disable_fixed} }, +% sub { $_[0]->{disable_fixed} }, % }, % 'S' => { 'desc' => 'Selectable Choice', % 'condition' => @@ -229,7 +107,7 @@ that field. % % my @dbs = $hashref->{svcdb} % ? ( $hashref->{svcdb} ) -% : qw( svc_acct svc_domain svc_forward svc_www svc_broadband svc_phone svc_external ); +% : FS::part_svc->svc_tables(); % % tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs; % my $widget = new HTML::Widgets::SelectLayers( @@ -291,8 +169,9 @@ that field. % my $part_svc_column = $part_svc->part_svc_column($field); % my $value = $part_svc_column->columnvalue; % my $flag = $part_svc_column->columnflag; -% my $def = $defs{$layer}{$field}; -% my $desc = ref($def) ? $def->{desc} : $def; +% #my $def = $defs{$layer}{$field}; +% my $def = FS::part_svc->svc_table_fields($layer)->{$field}; +% my $label = $def->{'def_label'} || $def->{'label'}; % % if ( $bgcolor eq $bgcolor1 ) { % $bgcolor = $bgcolor2; @@ -301,14 +180,13 @@ that field. % } % % $html .= qq!<TR><TD CLASS="grid" BGCOLOR="$bgcolor" ALIGN="right">!. -% $field; -% $html .= "- <FONT SIZE=-1>$desc</FONT>" if $desc; -% $html .= "</TD>"; -% $flag = '' if ref($def) && $def->{type} eq 'disabled'; +% ( $label || $field ). +% "</TD>"; +% $flag = '' if $def->{type} eq 'disabled'; % % $html .= qq!<TD CLASS="grid" BGCOLOR="$bgcolor">!; % -% if ( ref($def) && $def->{type} eq 'disabled' ) { +% if ( $def->{type} eq 'disabled' ) { % % $html .= 'No default'; % @@ -372,7 +250,7 @@ that field. % my $disabled = $flag ? '' % : 'DISABLED STYLE="background-color: #dddddd"'; % -% if ( ! ref($def) || $def->{type} eq 'text' ) { +% if ( !$def->{type} || $def->{type} eq 'text' ) { % % my $nodisplay = ' STYLE="display:none"'; % my $is_inv = ( $flag =~ /^[MA]$/ ); diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html index 96d568754..4b1d2c840 100644 --- a/httemplate/edit/process/elements/process.html +++ b/httemplate/edit/process/elements/process.html @@ -19,6 +19,8 @@ % # OR % # 'redirect' => 'view/table.cgi?', # value of primary key is appended % # +% # 'error_redirect' => popurl(2).'edit/table.cgi?', #query string appended +% # % # 'edit_ext' => 'html', #defaults to 'html', you might want 'cgi' while the % # #naming is still inconsistent % # @@ -78,7 +80,8 @@ % if ( $error ) { % $cgi->param('error', $error); % my $edit_ext = $opt{'edit_ext'} || 'html'; -% print $cgi->redirect(popurl(2). "$table.$edit_ext?". $cgi->query_string ); +% my $url = $opt{'error_redirect'} || popurl(2)."$table.$edit_ext?"; +% print $cgi->redirect($url. $cgi->query_string ); % } elsif ( $opt{'redirect'} ) { % print $cgi->redirect( $opt{'redirect'}. $pkeyvalue ); % } else { diff --git a/httemplate/edit/process/svc_Common.html b/httemplate/edit/process/svc_Common.html new file mode 100644 index 000000000..f5c869a12 --- /dev/null +++ b/httemplate/edit/process/svc_Common.html @@ -0,0 +1,13 @@ +<%init> + +$cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unparsable svcdb"; +my $table = $1; +require "FS/$table.pm"; + +</%init> +<% include( 'elements/svc_Common.html', + 'table' => $table, + 'redirect' => popurl(3)."view/svc_Common.html?svcdb=$table;svcnum=", + 'error_redirect' => popurl(3)."edit/svc_Common.html?svcdb=$table;", + ) +%> diff --git a/httemplate/edit/svc_Common.html b/httemplate/edit/svc_Common.html new file mode 100644 index 000000000..6393f9ebc --- /dev/null +++ b/httemplate/edit/svc_Common.html @@ -0,0 +1,30 @@ +<%init> + +# false laziness w/view/svc_Common.html + +$cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unparsable svcdb"; +my $table = $1; +require "FS/$table.pm"; + +my %opt; +if ( UNIVERSAL::can("FS::$table", 'table_info') ) { + $opt{'name'} = "FS::$table"->table_info->{'name'}; + + my $fields = "FS::$table"->table_info->{'fields'}; + my %labels = map { $_ => ( ref($fields->{$_}) + ? $fields->{$_}{'label'} + : $fields->{$_} + ); + } + keys %$fields; + $opt{'labels'} = \%labels; + +} + +</%init> +<% include('elements/svc_Common.html', + 'table' => $table, + 'post_url' => popurl(1). "process/svc_Common.html", + %opt, + ) +%> diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi index f552967e7..f42c14618 100755 --- a/httemplate/edit/svc_acct.cgi +++ b/httemplate/edit/svc_acct.cgi @@ -18,39 +18,39 @@ % die "No part_svc entry for svcpart $svcpart!" unless $part_svc; % @groups = $cgi->param('radius_usergroup'); % -%} else { +%} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding % -% my($query) = $cgi->keywords; -% if ( $query =~ /^(\d+)$/ ) { #editing -% $svcnum=$1; -% $svc_acct=qsearchs('svc_acct',{'svcnum'=>$svcnum}) -% or die "Unknown (svc_acct) svcnum!"; +% $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum'; +% $pkgnum = $1; +% $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart'; +% $svcpart = $1; % -% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) -% or die "Unknown (cust_svc) svcnum!"; +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; % -% $pkgnum=$cust_svc->pkgnum; -% $svcpart=$cust_svc->svcpart; +% $svc_acct = new FS::svc_acct({svcpart => $svcpart}); % -% $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } ); -% die "No part_svc entry for svcpart $svcpart!" unless $part_svc; +% $svcnum=''; % -% @groups = $svc_acct->radius_groups; +%} else { #editing % -% } else { #adding +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ or die "unparsable svcnum"; +% $svcnum=$1; +% $svc_acct=qsearchs('svc_acct',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_acct) svcnum!"; % -% foreach $_ (split(/-/,$query)) { -% $pkgnum=$1 if /^pkgnum(\d+)$/; -% $svcpart=$1 if /^svcpart(\d+)$/; -% } -% $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } ); -% die "No part_svc entry for svcpart $svcpart!" unless $part_svc; +% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) +% or die "Unknown (cust_svc) svcnum!"; % -% $svc_acct = new FS::svc_acct({svcpart => $svcpart}); +% $pkgnum=$cust_svc->pkgnum; +% $svcpart=$cust_svc->svcpart; % -% $svcnum=''; +% $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } ); +% die "No part_svc entry for svcpart $svcpart!" unless $part_svc; +% +% @groups = $svc_acct->radius_groups; % -% } %} % %my( $cust_pkg, $cust_main ) = ( '', '' ); diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi index 6b9535508..30eb6319f 100644 --- a/httemplate/edit/svc_broadband.cgi +++ b/httemplate/edit/svc_broadband.cgi @@ -1,10 +1,6 @@ -<!-- mason kludge --> -% -% %# If it's stupid but it works, it's still stupid. %# -Kristian % -% %use HTML::Widgets::SelectLayers; %use Tie::IxHash; % diff --git a/httemplate/edit/svc_domain.cgi b/httemplate/edit/svc_domain.cgi index 19e0e1285..5ec074bda 100755 --- a/httemplate/edit/svc_domain.cgi +++ b/httemplate/edit/svc_domain.cgi @@ -1,8 +1,7 @@ -% -% %my($svcnum, $pkgnum, $svcpart, $kludge_action, $purpose, $part_svc, % $svc_domain); %if ( $cgi->param('error') ) { +% % $svc_domain = new FS::svc_domain ( { % map { $_, scalar($cgi->param($_)) } fields('svc_domain') % } ); @@ -13,40 +12,41 @@ % $purpose = $cgi->param('purpose'); % $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } ); % die "No part_svc entry!" unless $part_svc; -%} else { -% $kludge_action = ''; -% $purpose = ''; -% my($query) = $cgi->keywords; -% if ( $query =~ /^(\d+)$/ ) { #editing -% $svcnum=$1; -% $svc_domain=qsearchs('svc_domain',{'svcnum'=>$svcnum}) -% or die "Unknown (svc_domain) svcnum!"; % -% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) -% or die "Unknown (cust_svc) svcnum!"; +%} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding +% +% $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum'; +% $pkgnum = $1; +% $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart'; +% $svcpart = $1; % -% $pkgnum=$cust_svc->pkgnum; -% $svcpart=$cust_svc->svcpart; +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; % -% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); -% die "No part_svc entry!" unless $part_svc; +% $svc_domain = new FS::svc_domain({}); % -% } else { #adding +% $svcnum=''; % -% $svc_domain = new FS::svc_domain({}); -% -% foreach $_ (split(/-/,$query)) { -% $pkgnum=$1 if /^pkgnum(\d+)$/; -% $svcpart=$1 if /^svcpart(\d+)$/; -% } -% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); -% die "No part_svc entry!" unless $part_svc; +% $svc_domain->set_default_and_fixed; % -% $svcnum=''; +%} else { #editing % -% $svc_domain->set_default_and_fixed; +% $kludge_action = ''; +% $purpose = ''; +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ or die "unparsable svcnum"; +% $svcnum=$1; +% $svc_domain=qsearchs('svc_domain',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_domain) svcnum!"; % -% } +% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) +% or die "Unknown (cust_svc) svcnum!"; +% +% $pkgnum=$cust_svc->pkgnum; +% $svcpart=$cust_svc->svcpart; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; % %} %my $action = $svcnum ? 'Edit' : 'Add'; diff --git a/httemplate/edit/svc_external.cgi b/httemplate/edit/svc_external.cgi index 1230340ac..393e71c38 100644 --- a/httemplate/edit/svc_external.cgi +++ b/httemplate/edit/svc_external.cgi @@ -1,8 +1,6 @@ -<!-- mason kludge --> -% -% %my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_external ); %if ( $cgi->param('error') ) { +% % $svc_external = new FS::svc_external ( { % map { $_, scalar($cgi->param($_)) } fields('svc_external') % } ); @@ -11,38 +9,40 @@ % $svcpart = $cgi->param('svcpart'); % $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); % die "No part_svc entry!" unless $part_svc; -%} else { -% my($query) = $cgi->keywords; -% if ( $query =~ /^(\d+)$/ ) { #editing -% $svcnum=$1; -% $svc_external=qsearchs('svc_external',{'svcnum'=>$svcnum}) -% or die "Unknown (svc_external) svcnum!"; % -% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) -% or die "Unknown (cust_svc) svcnum!"; +%} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding % -% $pkgnum=$cust_svc->pkgnum; -% $svcpart=$cust_svc->svcpart; -% -% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); -% die "No part_svc entry!" unless $part_svc; +% $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum'; +% $pkgnum = $1; +% $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart'; +% $svcpart = $1; % -% } else { #adding +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; % -% foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart -% $pkgnum=$1 if /^pkgnum(\d+)$/; -% $svcpart=$1 if /^svcpart(\d+)$/; -% } -% $svc_external = new FS::svc_external { svcpart => $svcpart }; +% $svc_external = new FS::svc_external { svcpart => $svcpart }; % -% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); -% die "No part_svc entry!" unless $part_svc; +% $svcnum=''; % -% $svcnum=''; +% $svc_external->set_default_and_fixed; % -% $svc_external->set_default_and_fixed; +%} else { #adding +% +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ or die "unparsable svcnum"; +% $svcnum=$1; +% $svc_external=qsearchs('svc_external',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_external) svcnum!"; +% +% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) +% or die "Unknown (cust_svc) svcnum!"; +% +% $pkgnum=$cust_svc->pkgnum; +% $svcpart=$cust_svc->svcpart; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; % -% } %} %my $action = $svc_external->svcnum ? 'Edit' : 'Add'; % diff --git a/httemplate/edit/svc_forward.cgi b/httemplate/edit/svc_forward.cgi index 73b32dc7b..ef08ffc16 100755 --- a/httemplate/edit/svc_forward.cgi +++ b/httemplate/edit/svc_forward.cgi @@ -13,39 +13,40 @@ % $svcpart = $cgi->param('svcpart'); % $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); % die "No part_svc entry!" unless $part_svc; -%} else { % -% my($query) = $cgi->keywords; +%} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding +% +% $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum'; +% $pkgnum = $1; +% $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart'; +% $svcpart = $1; % -% if ( $query =~ /^(\d+)$/ ) { #editing -% $svcnum=$1; -% $svc_forward=qsearchs('svc_forward',{'svcnum'=>$svcnum}) -% or die "Unknown (svc_forward) svcnum!"; +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; % -% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) -% or die "Unknown (cust_svc) svcnum!"; +% $svc_forward = new FS::svc_forward({}); % -% $pkgnum=$cust_svc->pkgnum; -% $svcpart=$cust_svc->svcpart; -% -% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); -% die "No part_svc entry!" unless $part_svc; +% $svcnum=''; % -% } else { #adding +% $svc_forward->set_default_and_fixed; % -% $svc_forward = new FS::svc_forward({}); +%} else { #editing % -% foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart -% $pkgnum=$1 if /^pkgnum(\d+)$/; -% $svcpart=$1 if /^svcpart(\d+)$/; -% } -% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); -% die "No part_svc entry!" unless $part_svc; +% my($query) = $cgi->keywords; % -% $svcnum=''; +% $query =~ /^(\d+)$/ or die "unparsable svcnum"; +% $svcnum=$1; +% $svc_forward=qsearchs('svc_forward',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_forward) svcnum!"; % -% $svc_forward->set_default_and_fixed; -% } +% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) +% or die "Unknown (cust_svc) svcnum!"; +% +% $pkgnum=$cust_svc->pkgnum; +% $svcpart=$cust_svc->svcpart; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; % %} %my $action = $svc_forward->svcnum ? 'Edit' : 'Add'; diff --git a/httemplate/edit/svc_www.cgi b/httemplate/edit/svc_www.cgi index 30d98f08b..4b27752ff 100644 --- a/httemplate/edit/svc_www.cgi +++ b/httemplate/edit/svc_www.cgi @@ -1,10 +1,9 @@ -<!-- mason kludge --> -% -% %my $conf = new FS::Conf; % %my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_www ); +% %if ( $cgi->param('error') ) { +% % $svc_www = new FS::svc_www ( { % map { $_, scalar($cgi->param($_)) } fields('svc_www') % } ); @@ -13,38 +12,40 @@ % $svcpart = $cgi->param('svcpart'); % $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); % die "No part_svc entry!" unless $part_svc; -%} else { -% my($query) = $cgi->keywords; -% if ( $query =~ /^(\d+)$/ ) { #editing -% $svcnum=$1; -% $svc_www=qsearchs('svc_www',{'svcnum'=>$svcnum}) -% or die "Unknown (svc_www) svcnum!"; % -% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) -% or die "Unknown (cust_svc) svcnum!"; +%} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding % -% $pkgnum=$cust_svc->pkgnum; -% $svcpart=$cust_svc->svcpart; -% -% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); -% die "No part_svc entry!" unless $part_svc; +% $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum'; +% $pkgnum = $1; +% $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart'; +% $svcpart = $1; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; % -% } else { #adding +% $svc_www = new FS::svc_www { svcpart => $svcpart }; % -% foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart -% $pkgnum=$1 if /^pkgnum(\d+)$/; -% $svcpart=$1 if /^svcpart(\d+)$/; -% } -% $svc_www = new FS::svc_www { svcpart => $svcpart }; +% $svcnum=''; % -% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); -% die "No part_svc entry!" unless $part_svc; +% $svc_www->set_default_and_fixed; % -% $svcnum=''; +%} else { #editing % -% $svc_www->set_default_and_fixed; +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ or die "unparsable svcnum"; +% $svcnum=$1; +% $svc_www=qsearchs('svc_www',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_www) svcnum!"; +% +% my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) +% or die "Unknown (cust_svc) svcnum!"; +% +% $pkgnum=$cust_svc->pkgnum; +% $svcpart=$cust_svc->svcpart; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; % -% } %} %my $action = $svc_www->svcnum ? 'Edit' : 'Add'; % diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html index c543ac040..0e69b19ca 100644 --- a/httemplate/elements/header.html +++ b/httemplate/elements/header.html @@ -132,7 +132,7 @@ input.fsblackbuttonselected { <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right"> <FORM ACTION="<%$fsurl%>search/cust_main.cgi" METHOD="GET" STYLE="margin:0"> <INPUT NAME="search_cust" TYPE="text" VALUE="(cust #, name, company or phone)" SIZE="28" onFocus="clearhint_search_cust(this);" onClick="clearhint_search_cust(this);" STYLE="vertical-align:bottom;text-align:right"><BR> - <A HREF="<%$fsurl%>search/cust_main.html" STYLE="color: #000000; font-size: 70%">Advanced</A> + <A NOTYET="<%$fsurl%>search/cust_main.html" STYLE="color: #000000; font-size: 70%">Advanced</A> <INPUT TYPE="submit" VALUE="Search customers" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%"> </FORM> </TD> @@ -155,10 +155,10 @@ input.fsblackbuttonselected { </TD> <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right"> - <FORM ACTION="<%$fsurl%>search/svc_Smart.html" METHOD="GET" STYLE="margin:0"> + <FORM ACTION="<%$fsurl%>search/cust_svc.html" METHOD="GET" STYLE="margin:0"> <INPUT NAME="search_svc" TYPE="text" VALUE="(user, user@domain or domain)" SIZE="26" onFocus="clearhint_search_svc(this);" onClick="clearhint_search_svc(this);" STYLE="vertical-align:bottom;text-align:right"><BR> - <A HREF="<%$fsurl%>search/svc_Smarter.html" STYLE="color: #000000; font-size: 70%">Advanced</A> - <INPUT TYPE="submit" VALUE="Search services"CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%"> + <A NOTYET="<%$fsurl%>search/svc_Smarter.html" STYLE="color: #000000; font-size: 70%">Advanced</A> + <INPUT TYPE="submit" VALUE="Search services" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%"> </FORM> </TD> diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index 328e24a29..18a499b7b 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -80,58 +80,49 @@ tie my %report_invoices, 'Tie::IxHash', 'Advanced invoice reports' => [ $fsurl.'search/report_cust_bill.html', 'by agent, date range, etc.' ], ; -tie my %report_services_acct, 'Tie::IxHash', - 'All accounts by username' => [ $fsurl.'search/svc_acct.cgi?username', '' ], - 'All accounts by UID' => [ $fsurl.'search/svc_acct.cgi?uid', '' ], -; -$report_services_acct{'Unlinked accounts'} = [ $fsurl.'search/svc_acct.cgi?UN_username', 'Pre-Freeside accounts without a customer record' ] - if $curuser->access_right('View/link unlinked services'); - -tie my %report_services_domain, 'Tie::IxHash', - 'All domains' => [ $fsurl.'search/svc_domain.cgi?domain', '' ], -; -$report_services_domain{'Unlinked domains'} = [ $fsurl.'search/svc_domain.cgi?UN_domain', 'Pre-Freeside domains without a customer record' ] - if $curuser->access_right('View/link unlinked services'); - -tie my %report_services_forward, 'Tie::IxHash', - 'All mail forwards' => [ $fsurl.'search/svc_forward.cgi?svcnum', '' ], -; -$report_services_forward{'Unlinked mail forwards'} = [ $fsurl.'search/svc_forward.cgi?UN_svcnum', 'Pre-Freeside mail forwards without a customer record' ] - if $curuser->access_right('View/link unlinked services'); - -tie my %report_services_www, 'Tie::IxHash', - 'All virtual hosts' => [ $fsurl.'search/svc_www.cgi?svcnum', '' ], -; -$report_services_www{'Unlinked virtual hosts'} = [ $fsurl.'search/svc_www.cgi?UN_svcnum', 'Pre-Freeside virtual hosts without a customer record' ] - if $curuser->access_right('View/link unlinked services'); - -tie my %report_services_broadband, 'Tie::IxHash', - 'All broadband services' => [ $fsurl.'search/svc_broadband.cgi?svcnum', '' ], - #'Unlinked domain' => [ $fsurl.'search/svc_acct.cgi?UN_uid', 'Pre-Freeside broadband services without a customer record' ], -; - -tie my %report_services_phone, 'Tie::IxHash', - 'All phone numbers' => [ $fsurl.'search/svc_phone.cgi?svcnum', '' ], -; - -tie my %report_services_external, 'Tie::IxHash', - 'All external services' => [ $fsurl.'search/svc_external.cgi?id', '' ], -; -$report_services_external{'Unlinked external services'} = [ $fsurl.'search/svc_external.cgi?UN_id', 'Pre-Freeside external services without a customer record' ] - if $curuser->access_right('View/link unlinked services'); - tie my %report_services, 'Tie::IxHash'; if ( $curuser->access_right('Configuration') ) { $report_services{'Service definitions'} = [ $fsurl.'browse/part_svc.cgi?orderby=active', 'Service definitions by number of active packages' ]; $report_services{'separator'} = ''; } -$report_services{'Accounts'} = [ \%report_services_acct, 'Access accounts and mailboxes' ]; -$report_services{'Domains'} = [ \%report_services_domain, 'Domains', ]; -$report_services{'Mail forwards'} = [ \%report_services_forward, 'Mail forwards', ]; -$report_services{'Virtual hosts'} = [ \%report_services_www, 'Virtual hosting', ]; -$report_services{'Broadband services'} = [ \%report_services_broadband, 'Fixed (username-less) broadband services', ]; -$report_services{'Phone numbers'} = [ \%report_services_phone, 'Telephone numbers', ]; -$report_services{'External services'} = [ \%report_services_external, 'External services', ]; +foreach my $svcdb ( FS::part_svc->svc_tables() ) { + + my $name = "FS::$svcdb"->table_info->{'name_plural'} + || PL( "FS::$svcdb"->table_info->{'name'} ); + my $lcname = lc($name); + my $longname = "FS::$svcdb"->table_info->{'longname_plural'} || $name; + my $lclongname = lc($longname); + my $sorts = "FS::$svcdb"->table_info->{'sorts'} || [ 'svcnum' ]; + $sorts = [ $sorts ] unless ref($sorts); + my %svc_url = ( 'm' => $m, + 'action' => 'search', + 'svcdb' => $svcdb, + ); + + tie my %report_svc, 'Tie::IxHash'; + + foreach my $sort ( @$sorts ) { + + my $title = "All $lcname"; + $title .= " by ". FS::part_svc->svc_table_fields($svcdb)->{$sort}->{'label'} + if scalar(@$sorts) > 1; + + $report_svc{$title} = + [ FS::UI::Web::svc_url( %svc_url, 'query' => "magic=all;sortby=$sort" ), + '', + ]; + } + + if ( $curuser->access_right('View/link unlinked services') ) { + $report_svc{"Unlinked $lcname"} = + [ FS::UI::Web::svc_url( %svc_url, 'query' => "magic=unlinked;sortby=". $sorts->[0] ), + "Pre-Freeside $lcname without a customer record", + ]; + } + + $report_services{$name} = [ \%report_svc, $longname ]; + +} tie my %report_packages, 'Tie::IxHash'; if ( $curuser->access_right('Configuration') ) { diff --git a/httemplate/misc/link.cgi b/httemplate/misc/link.cgi index 1d1f5e133..ef72b4a5c 100755 --- a/httemplate/misc/link.cgi +++ b/httemplate/misc/link.cgi @@ -1,6 +1,3 @@ -<!-- mason kludge --> -% -% %my %link_field = ( % 'svc_acct' => 'username', % 'svc_domain' => 'domain', @@ -16,12 +13,10 @@ % }, %); % -%my($query) = $cgi->keywords; -%my($pkgnum, $svcpart) = ('', ''); -%foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart -% $pkgnum=$1 if /^pkgnum(\d+)$/; -% $svcpart=$1 if /^svcpart(\d+)$/; -%} +%$cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum'; +%my $pkgnum = $1; +%$cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart'; +%my $svcpart = $1; % %my $part_svc = qsearchs('part_svc',{'svcpart'=>$svcpart}); %my $svc = $part_svc->getfield('svc'); @@ -29,8 +24,6 @@ %my $link_field = $link_field{$svcdb}; %my $link_field2 = $link_field2{$svcdb}; % -% - <% include("/elements/header.html","Link to existing $svc") %> <FORM ACTION="<% popurl(1) %>process/link.cgi" METHOD=POST> diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi index 1b6b52675..e15447ae0 100755 --- a/httemplate/search/cust_main.cgi +++ b/httemplate/search/cust_main.cgi @@ -1,5 +1,3 @@ -% -% %my $conf = new FS::Conf; %my $maxrecords = $conf->config('maxsearchrecordsperpage'); % @@ -486,8 +484,9 @@ % my($label, $value, $svcdb) = $cust_svc->label; % my($svcnum) = $cust_svc->svcnum; % my($sview) = $p.'view'; -% print $n2,qq!<TD CLASS="grid" BGCOLOR="$bgcolor" ><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$label</FONT></A></TD>!, -% qq!<TD CLASS="grid" BGCOLOR="$bgcolor" ><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$value</FONT></A></TD>!; +% print $n2, +% qq!<TD CLASS="grid" BGCOLOR="$bgcolor" >!. FS::UI::Web::svc_link($m, $cust_svc->part_svc, $cust_svc) . qq!</TD> !. +% qq!<TD CLASS="grid" BGCOLOR="$bgcolor" >!. FS::UI::Web::svc_label_link($m, $cust_svc->part_svc, $cust_svc) . qq!</TD> !; % $n2="</TR><TR>"; % } % @@ -719,6 +718,3 @@ % % \@cust_main; %} -% -% - diff --git a/httemplate/search/cust_svc.html b/httemplate/search/cust_svc.html new file mode 100644 index 000000000..568b43b18 --- /dev/null +++ b/httemplate/search/cust_svc.html @@ -0,0 +1,110 @@ +<% include( 'elements/search.html', + 'title' => 'Service search results', + 'name' => 'services', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + # package? + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + sub { + #$_[0]->svc. ': '. $_[0]->label; + my($label, $value, $svcdb) = $_[0]->label; + "$label: $value"; + }, + # package? + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + # package? + ( map { $link_cust } + FS::UI::Web::cust_header() + ), + ], + ) +%> +<%init> + +my $addl_from = ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +my @extra_sql = (); +my $orderby = 'ORDER BY svcnum'; #has to be ordered by something + #for pagination to work +if ( length( $cgi->param('search_svc') ) ) { + + my $string = $cgi->param('search_svc'); + + # implement fuzzy searching in subclasses too at some point? + # service searching maybe shouldn't be fuzzy... + + push @extra_sql, + ' ( '. join(' OR ', + map { my $table = $_; + my $search_sql = "FS::$table"->search_sql($string); + " ( svcdb = '$table' + AND 0 < ( SELECT COUNT(*) FROM $table + WHERE $table.svcnum = cust_svc.svcnum + AND $search_sql + ) + ) "; + } + FS::part_svc->svc_tables + ). ' ) '; + +} elsif ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { + + $cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unknown svcdb"; + push @extra_sql, "svcdb = '$1'"; + + push @extra_sql, 'pkgnum IS NULL' + if $cgi->param('magic') eq 'unlinked'; + + if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { + my $sortby = $1; + $orderby = "ORDER BY $sortby"; + } + +} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { + + push @extra_sql, "svcpart = $1"; + +} else { + eidiot("No search term specified"); +} + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql; + +my $extra_sql = ' WHERE '. join(' AND ', @extra_sql ); + +my $sql_query = { + 'select' => join(', ', + 'cust_svc.*', + 'part_svc.*', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'table' => 'cust_svc', + 'addl_from' => $addl_from, + 'hashref' => {}, + 'extra_sql' => "$extra_sql $orderby", +}; + +my $count_query = "SELECT COUNT(*) FROM cust_svc $addl_from $extra_sql"; + +my $link = sub { + my $cust_svc = shift; + my $url = FS::UI::Web::svc_url( + 'm' => $m, + 'action' => 'view', + #'part_svc' => $cust_svc->part_svc, + 'svcdb' => $cust_svc->svcdb, #we have it from the joined search + #'svc' => $cust_svc, #redundant + 'query' => 'svcnum=', + ); diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi index 2a1414bea..592aa150a 100755 --- a/httemplate/search/svc_acct.cgi +++ b/httemplate/search/svc_acct.cgi @@ -1,15 +1,4 @@ -% -% -%my $orderby = 'ORDER BY svcnum'; -% -%my($query)=$cgi->keywords; -%$query ||= ''; #to avoid use of unitialized value errors -% %my @extra_sql = (); -%if ( $query =~ /^UN_(.*)$/ ) { -% $query = $1; -% push @extra_sql, 'pkgnum IS NULL'; -%} % % if ( $cgi->param('domain') ) { % my $svc_domain = @@ -23,13 +12,21 @@ % } % } % -%if ( $query eq 'svcnum' ) { -% #$orderby = "ORDER BY svcnum"; -%} elsif ( $query eq 'username' ) { -% $orderby = "ORDER BY LOWER(username)"; -%} elsif ( $query eq 'uid' ) { -% $orderby = "ORDER BY uid"; -% push @extra_sql, "uid IS NOT NULL"; +%my $orderby = 'ORDER BY svcnum'; +%if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { +% +% push @extra_sql, 'pkgnum IS NULL' +% if $cgi->param('magic') eq 'unlinked'; +% +% if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { +% my $sortby = $1; +% $sortby = "LOWER($sortby)" +% if $sortby eq 'username'; +% push @extra_sql, "$sortby IS NOT NULL" +% if $sortby eq 'uid'; +% $orderby = "ORDER BY $sortby"; +% } +% %} elsif ( $cgi->param('popnum') =~ /^(\d+)$/ ) { % push @extra_sql, "popnum = $1"; % $orderby = "ORDER BY LOWER(username)"; diff --git a/httemplate/search/svc_broadband.cgi b/httemplate/search/svc_broadband.cgi index ae32ccd7e..297d74c1d 100755 --- a/httemplate/search/svc_broadband.cgi +++ b/httemplate/search/svc_broadband.cgi @@ -1,19 +1,38 @@ -% -% %my $conf = new FS::Conf; % -%my($query)=$cgi->keywords; -%$query ||= ''; #to avoid use of unitialized value errors -%my(@svc_broadband,$sortby); -%if ( $query eq 'svcnum' ) { -% $sortby=\*svcnum_sort; -% @svc_broadband=qsearch('svc_broadband',{}); -%} elsif ( $query eq 'blocknum' ) { -% $sortby=\*blocknum_sort; +%my @svc_broadband = (); +%my $sortby=\*svcnum_sort; +%if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { +% % @svc_broadband=qsearch('svc_broadband',{}); -%} else { -% $cgi->param('ip_addr') =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/; -% my($ip_addr)=$1; +% +% if ( $cgi->param('magic') eq 'unlinked' ) { +% @svc_broadband = grep { qsearchs('cust_svc', { +% 'svcnum' => $_->svcnum, +% 'pkgnum' => '', +% } +% ) +% } +% @svc_broadband; +% } +% +% if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { +% my $sortby = $1; +% if ( $sortby eq 'blocknum' ) { +% $sortby = \*blocknum_sort; +% } +% } +% +%} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { +% +% @svc_broadband = +% qsearch( 'svc_broadband', {}, '', +% " WHERE $1 = ( SELECT svcpart FROM cust_svc ". +% " WHERE cust_svc.svcnum = svc_external.svcnum ) " +% ); +% +%} elsif ( $cgi->param('ip_addr') =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/ ) { +% my $ip_addr = $1; % @svc_broadband = qsearchs('svc_broadband',{'ip_addr'=>$ip_addr}); %} % diff --git a/httemplate/search/svc_domain.cgi b/httemplate/search/svc_domain.cgi index 85ae94a80..8643ea0dc 100755 --- a/httemplate/search/svc_domain.cgi +++ b/httemplate/search/svc_domain.cgi @@ -1,27 +1,19 @@ -% -% %my $conf = new FS::Conf; % -%my($query)=$cgi->keywords; -%$query ||= ''; #to avoid use of unitialized value errors -% %my $orderby = 'ORDER BY svcnum'; %my %svc_domain = (); %my @extra_sql = (); -%if ( $query eq 'svcnum' ) { -% #$orderby = 'ORDER BY svcnum'; -%} elsif ( $query eq 'domain' ) { -% $orderby = 'ORDER BY domain'; -%} elsif ( $query eq 'UN_svcnum' ) { #UN searches need to be acl'ed (and need to -% #fix $agentnums_sql -% #$orderby = 'ORDER BY svcnum'; -% push @extra_sql, 'pkgnum IS NULL'; -%} elsif ( $query eq 'UN_domain' ) { #UN searches need to be acl'ed (and need to -% #fix $agentnums_sql -% $orderby = 'ORDER BY domain'; -% push @extra_sql, 'pkgnum IS NULL'; +%if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { +% +% push @extra_sql, 'pkgnum IS NULL' +% if $cgi->param('magic') eq 'unlinked'; +% +% if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { +% my $sortby = $1; +% $orderby = "ORDER BY $sortby"; +% } +% %} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { -% #$orderby = 'ORDER BY svcnum'; % push @extra_sql, "svcpart = $1"; %} else { % $cgi->param('domain') =~ /^([\w\-\.]+)$/; diff --git a/httemplate/search/svc_external.cgi b/httemplate/search/svc_external.cgi index e85d6d7b3..5502bfc25 100755 --- a/httemplate/search/svc_external.cgi +++ b/httemplate/search/svc_external.cgi @@ -1,39 +1,45 @@ -% -% %my $conf = new FS::Conf; % -%my($query)=$cgi->keywords; -%$query ||= ''; #to avoid use of unitialized value errors -%my(@svc_external,$sortby); -%if ( $query eq 'svcnum' ) { -% $sortby=\*svcnum_sort; -% @svc_external=qsearch('svc_external',{}); -%} elsif ( $query eq 'id' ) { -% $sortby=\*id_sort; +%my @svc_external = (); +%my @h_svc_external = (); +%my $sortby=\*svcnum_sort; +%if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { +% % @svc_external=qsearch('svc_external',{}); -%} elsif ( $query eq 'UN_svcnum' ) { -% $sortby=\*svcnum_sort; -% @svc_external = grep qsearchs('cust_svc',{ -% 'svcnum' => $_->svcnum, -% 'pkgnum' => '', -% }), qsearch('svc_external',{}); -%} elsif ( $query eq 'UN_id' ) { -% $sortby=\*id_sort; -% @svc_external = grep qsearchs('cust_svc',{ -% 'svcnum' => $_->svcnum, -% 'pkgnum' => '', -% }), qsearch('svc_external',{}); +% +% if ( $cgi->param('magic') eq 'unlinked' ) { +% @svc_external = grep { qsearchs('cust_svc', { +% 'svcnum' => $_->svcnum, +% 'pkgnum' => '', +% } +% ) +% } +% @svc_external; +% } +% +% if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { +% my $sortby = $1; +% if ( $sortby eq 'id' ) { +% $sortby = \*id_sort; +% } +% } +% %} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { +% % @svc_external = % qsearch( 'svc_external', {}, '', % " WHERE $1 = ( SELECT svcpart FROM cust_svc ". % " WHERE cust_svc.svcnum = svc_external.svcnum ) " % ); -% $sortby=\*svcnum_sort; -%} else { -% $cgi->param('id') =~ /^([\w\-\.]+)$/; -% my($id)=$1; -% #push @svc_domain, qsearchs('svc_domain',{'domain'=>$domain}); +% +%} elsif ( $cgi->param('title') =~ /^(.*)$/ ) { +% $sortby=\*id_sort; +% @svc_external=qsearch('svc_external',{ title => $1 }); +% if( $cgi->param('history') == 1 ) { +% @h_svc_external=qsearch('h_svc_external',{ title => $1 }); +% } +%} elsif ( $cgi->param('id') =~ /^([\w\-\.]+)$/ ) { +% my $id = $1; % @svc_external = qsearchs('svc_external',{'id'=>$id}); %} % @@ -84,7 +90,46 @@ % print "</TR>"; % % } -% +% if( scalar(@h_svc_external) > 0 ) { +% print <<HTML; +% </TABLE> +% <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0> +% <TR> +% <TH>Freeside ID</TH> +% <TH>Service #</TH> +% <TH>Title</TH> +% <TH>Date</TH> +% </TR> +%HTML +% +% foreach my $h_svc ( @h_svc_external ) { +% my($svcnum, $id, $title, $user, $date)=( +% $h_svc->svcnum, +% $h_svc->id, +% $h_svc->title, +% $h_svc->history_user, +% $h_svc->history_date, +% ); +% my $rowspan = 1; +% my ($h_cust_svc) = qsearchs( 'h_cust_svc', { +% svcnum => $svcnum, +% }); +% my $cust_pkg = qsearchs( 'cust_pkg', { +% pkgnum => $h_cust_svc->pkgnum, +% }); +% my $custnum = $cust_pkg->custnum; +% +% print <<END; +% <TR> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$custnum</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$svcnum</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$title</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$date</A></TD> +% </TR> +%END +% } +% } +% % print <<END; % </TABLE> % </BODY> diff --git a/httemplate/search/svc_forward.cgi b/httemplate/search/svc_forward.cgi index dc002d96a..4d44c9ca6 100755 --- a/httemplate/search/svc_forward.cgi +++ b/httemplate/search/svc_forward.cgi @@ -1,23 +1,19 @@ -% -% %my $conf = new FS::Conf; % -%my($query)=$cgi->keywords; -%$query ||= ''; #to avoid use of unitialized value errors +%my $orderby = 'ORDER BY svcnum'; +%my @extra_sql = (); +%if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { % -%my $orderby; +% push @extra_sql, 'pkgnum IS NULL' +% if $cgi->param('magic') eq 'unlinked'; % -%my @extra_sql = (); -%if ( $query =~ /^UN_(.*)$/ ) { #UN searches need to be acl'ed (and need to -% #fix $agentnums_sql -% $query = $1; -% push @extra_sql, 'pkgnum IS NULL'; -%} +% if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { +% my $sortby = $1; +% $orderby = "ORDER BY $sortby"; +% } % -%if ( $query eq 'svcnum' ) { -% $orderby = 'ORDER BY svcnum'; -%} else { -% eidiot('unimplemented'); +%} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { +% push @extra_sql, "svcpart = $1"; %} % %my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. diff --git a/httemplate/search/svc_phone.cgi b/httemplate/search/svc_phone.cgi index 26e2090e6..229dd5d89 100644 --- a/httemplate/search/svc_phone.cgi +++ b/httemplate/search/svc_phone.cgi @@ -1,19 +1,19 @@ -% -% %my $conf = new FS::Conf; % -%my($query)=$cgi->keywords; -%$query ||= ''; #to avoid use of unitialized value errors -% %my $orderby = 'ORDER BY svcnum'; %my %svc_phone = (); %my @extra_sql = (); -%if ( $query eq 'svcnum' ) { -% #$orderby = 'ORDER BY svcnum'; -%} elsif ( $query eq 'phonenum' ) { -% $orderby = 'ORDER BY phonenum'; +%if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { +% +% push @extra_sql, 'pkgnum IS NULL' +% if $cgi->param('magic') eq 'unlinked'; +% +% if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { +% my $sortby = $1; +% $orderby = "ORDER BY $sortby"; +% } +% %} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { -% #$orderby = 'ORDER BY svcnum'; % push @extra_sql, "svcpart = $1"; %} else { % $cgi->param('phonenum') =~ /^([\d\- ]+)$/; diff --git a/httemplate/search/svc_www.cgi b/httemplate/search/svc_www.cgi index b0f1d5c80..ae1482b9f 100755 --- a/httemplate/search/svc_www.cgi +++ b/httemplate/search/svc_www.cgi @@ -1,16 +1,35 @@ +%#my $conf = new FS::Conf; % +%my $orderby = 'ORDER BY svcnum'; +%my @extra_sql = (); +%if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { % -%#my $conf = new FS::Conf; +% push @extra_sql, 'pkgnum IS NULL' +% if $cgi->param('magic') eq 'unlinked'; % -%my($query)=$cgi->keywords; -%$query ||= ''; #to avoid use of unitialized value errors -%my $orderby; -%if ( $query eq 'svcnum' ) { -% $orderby = 'ORDER BY svcnum'; -%} else { -% eidiot('unimplemented'); +% if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { +% my $sortby = $1; +% $orderby = "ORDER BY $sortby"; +% } +% +%} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { +% push @extra_sql, "svcpart = $1"; %} % +%my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. +% ' LEFT JOIN part_svc USING ( svcpart ) '. +% ' LEFT JOIN cust_pkg USING ( pkgnum ) '. +% ' LEFT JOIN cust_main USING ( custnum ) '; +% +%#here is the agent virtualization +%push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql; +% +%my $extra_sql = +% scalar(@extra_sql) +% ? ' WHERE '. join(' AND ', @extra_sql ) +% : ''; +% +% %my $count_query = 'SELECT COUNT(*) FROM svc_www'; %my $sql_query = { % 'table' => 'svc_www', @@ -22,9 +41,7 @@ % FS::UI::Web::cust_sql_fields(), % ), % 'extra_sql' => $orderby, -% 'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum )'. -% 'LEFT JOIN cust_pkg USING ( pkgnum )'. -% 'LEFT JOIN cust_main USING ( custnum )', +% 'addl_from' => $addl_from, %}; % %my $link = [ "${p}view/svc_www.cgi?", 'svcnum', ]; diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html index 0a386f265..841605028 100755 --- a/httemplate/view/cust_main/packages.html +++ b/httemplate/view/cust_main/packages.html @@ -1,11 +1,9 @@ -% % my( $cust_main ) = @_; % my $conf = new FS::Conf; % % my $curuser = $FS::CurrentUser::CurrentUser; % % my $packages = get_packages($cust_main, $conf); -% <A NAME="cust_pkg"><FONT SIZE="+2">Packages</FONT></A> @@ -58,44 +56,42 @@ Current packages % my $bgcolor1 = '#eeeeee'; % my $bgcolor2 = '#ffffff'; % my $bgcolor = ''; -% - <TR> <TH CLASS="grid" BGCOLOR="#cccccc">Package</TH> <TH CLASS="grid" BGCOLOR="#cccccc">Status</TH> <TH CLASS="grid" BGCOLOR="#cccccc">Services</TH> </TR> + +%foreach my $cust_pkg (@$packages) { % -%foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) { +% my $part_pkg = $cust_pkg->part_pkg; % % if ( $bgcolor eq $bgcolor1 ) { % $bgcolor = $bgcolor2; % } else { % $bgcolor = $bgcolor1; % } -% -% -<!--pkgnum: <%$pkg->{pkgnum}%>--> +<!--pkgnum: <% $cust_pkg->pkgnum %>--> <TR> <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> - <A NAME="cust_pkg<%$pkg->{pkgnum}%>"><%$pkg->{pkgnum}%></A>: - <%$pkg->{pkg}%> - <%$pkg->{comment}%><BR> + <A NAME="cust_pkg<% $cust_pkg->pkgnum %>"><% $cust_pkg->pkgnum %></A>: + <% $part_pkg->pkg %> - <% $part_pkg->comment %><BR> <FONT SIZE=-1> -% unless ( $pkg->{cancel} ) { +% unless ( $cust_pkg->get('cancel') ) { % if ( $curuser->access_right('Change customer package') ) { - ( <%pkg_change_link($pkg)%> ) + ( <%pkg_change_link($cust_pkg)%> ) % } % if ( $curuser->access_right('Edit customer package dates') ) { - ( <%pkg_dates_link($pkg)%> ) + ( <%pkg_dates_link($cust_pkg)%> ) % } % if ( $curuser->access_right('Customize customer package') ) { - ( <%pkg_customize_link($pkg,$cust_main->custnum)%> ) + ( <%pkg_customize_link($cust_pkg,$cust_main->custnum)%> ) % } % } @@ -118,7 +114,7 @@ Current packages % % #false laziness w/edit/REAL_cust_pkg.cgi % my( $billed_or_prepaid, $last_bill_or_renewed, $next_bill_or_prepaid_until ); -% unless ( $pkg->{'part_pkg'}->is_prepaid ) { +% unless ( $part_pkg->is_prepaid ) { % $billed_or_prepaid = 'billed'; % $last_bill_or_renewed = 'Last bill'; % $next_bill_or_prepaid_until = 'Next bill'; @@ -129,19 +125,19 @@ Current packages % } % % -% if ( $pkg->{cancel} ) { +% if ( $cust_pkg->get('cancel') ) { <!-- #status: cancelled --> <TR> <TD WIDTH="<%$width%>" ALIGN="right"><FONT COLOR="#ff0000"><B>Cancelled </B></FONT></TD> - <% pkg_datestr($pkg,'cancel',$conf) %> + <% pkg_datestr($cust_pkg, 'cancel', $conf) %> </TR> <TR> <TD WIDTH="<%$width%>" ALIGN="right"><FONT COLOR="#ff0000" SIZE="-2"> - <% $pkg->{reason} %> + <% $cust_pkg->last_reason ? $cust_pkg->last_reason->reason : '' %> </FONT></TD> </TR> -% unless ( $pkg->{setup} ) { +% unless ( $cust_pkg->get('setup') ) { <TR> @@ -152,37 +148,37 @@ Current packages <TR> <TD WIDTH="<%$width%>" ALIGN="right">Setup </TD> - <% pkg_datestr($pkg, 'setup',$conf) %> + <% pkg_datestr($cust_pkg, 'setup', $conf) %> </TR> -% if ( $pkg->{'last_bill'} ) { +% if ( $cust_pkg->get('last_bill') ) { <TR> <TD WIDTH="<%$width%>" ALIGN="right"><% $last_bill_or_renewed %> </TD> - <% pkg_datestr($pkg, 'last_bill',$conf) %> + <% pkg_datestr($cust_pkg, 'last_bill',$conf) %> </TR> % } -% if ( $pkg->{'susp'} ) { +% if ( $cust_pkg->get('susp') ) { <TR> <TD WIDTH="<%$width%>" ALIGN="right">Suspended </TD> - <% pkg_datestr($pkg, 'susp',$conf) %> + <% pkg_datestr($cust_pkg, 'susp', $conf) %> </TR> % } % } % } else { -% if ( $pkg->{susp} ) { +% if ( $cust_pkg->get('susp') ) { <!-- #status: suspended --> <TR> <TD WIDTH="<%$width%>" ALIGN="right"><FONT COLOR="#FF9900"><B>Suspended</B> </FONT></TD> - <% pkg_datestr($pkg,'susp',$conf) %> + <% pkg_datestr($cust_pkg, 'susp', $conf) %> </TR> <TR> <TD WIDTH="<%$width%>" ALIGN="right"><FONT COLOR="#FF9900" SIZE="-2"> - <% $pkg->{reason} %> + <% $cust_pkg->last_reason ? $cust_pkg->last_reason->reason : '' %> </FONT></TD> </TR> -% unless ( $pkg->{setup} ) { +% unless ( $cust_pkg->get('setup') ) { <TR> @@ -193,24 +189,24 @@ Current packages <TR> <TD WIDTH="<%$width%>" ALIGN="right">Setup </TD> - <% pkg_datestr($pkg, 'setup',$conf) %> + <% pkg_datestr($cust_pkg, 'setup', $conf) %> </TR> % } -% if ( $pkg->{'last_bill'} ) { +% if ( $cust_pkg->get('last_bill') ) { <TR> <TD WIDTH="<%$width%>" ALIGN="right"><% $last_bill_or_renewed %> </TD> - <% pkg_datestr($pkg, 'last_bill',$conf) %> + <% pkg_datestr($cust_pkg, 'last_bill', $conf) %> </TR> % } <!-- # next bill ?? --> -% if ( $pkg->{'expire'} ) { +% if ( $cust_pkg->get('expire') ) { <TR> <TD WIDTH="<%$width%>" ALIGN="right">Expires </TD> - <% pkg_datestr($pkg, 'expire',$conf) %> + <% pkg_datestr($cust_pkg, 'expire', $conf) %> </TR> % } @@ -220,11 +216,11 @@ Current packages <FONT SIZE=-1> % if ( $curuser->access_right('Unsuspend customer package') ) { - ( <% pkg_unsuspend_link($pkg) %> ) + ( <% pkg_unsuspend_link($cust_pkg) %> ) % } % if ( $curuser->access_right('Cancel customer package') ) { - ( <% pkg_cancel_link($pkg) %> ) + ( <% pkg_cancel_link($cust_pkg) %> ) % } </FONT> @@ -232,9 +228,9 @@ Current packages </TR> % } else { <!-- #status: active --> -% unless ( $pkg->{setup} ) { +% unless ( $cust_pkg->get('setup') ) { <!-- #not setup --> -% unless ( $pkg->{'freq'} ) { +% unless ( $part_pkg->freq ) { <TR> @@ -246,7 +242,7 @@ Current packages <FONT SIZE=-1> % if ( $curuser->access_right('Cancel customer package immediately') ) { - ( <% pkg_cancel_link($pkg) %> ) + ( <% pkg_cancel_link($cust_pkg) %> ) % } </FONT> @@ -256,12 +252,12 @@ Current packages <TR> - <TD COLSPAN=<%$colspan%>>Not yet billed (<% $billed_or_prepaid %> <% myfreq($pkg->{part_pkg}) %>)</TD> + <TD COLSPAN=<%$colspan%>>Not yet billed (<% $billed_or_prepaid %> <% myfreq($part_pkg) %>)</TD> </TR> % } % } else { <!-- #setup --> -% unless ( $pkg->{freq} ) { +% unless ( $part_pkg->freq ) { <TR> @@ -270,58 +266,58 @@ Current packages <TR> <TD WIDTH="<%$width%>" ALIGN="right">Billed </TD> - <% pkg_datestr($pkg,'setup',$conf) %> + <% pkg_datestr($cust_pkg, 'setup', $conf) %> </TR> % } else { <TR> - <TD COLSPAN=<%$colspan%>><FONT COLOR="#00CC00"><B>Active</B></FONT>, <% $billed_or_prepaid %> <% myfreq($pkg->{part_pkg}) %></TD> + <TD COLSPAN=<%$colspan%>><FONT COLOR="#00CC00"><B>Active</B></FONT>, <% $billed_or_prepaid %> <% myfreq($part_pkg) %></TD> </TR> <TR> <TD WIDTH="<%$width%>" ALIGN="right">Setup </TD> - <% pkg_datestr($pkg, 'setup',$conf) %> + <% pkg_datestr($cust_pkg, 'setup', $conf) %> </TR> % } % } -% if ( $pkg->{'last_bill'} ) { +% if ( $cust_pkg->get('last_bill') ) { <TR> <TD WIDTH="<%$width%>" ALIGN="right"><% $last_bill_or_renewed %> </TD> - <% pkg_datestr($pkg, 'last_bill',$conf) %> + <% pkg_datestr($cust_pkg, 'last_bill', $conf) %> </TR> % } -% if ( $pkg->{'next_bill'} ) { +% if ( $cust_pkg->get('bill') ) { #next bill <TR> <TD WIDTH="<%$width%>" ALIGN="right"><% $next_bill_or_prepaid_until %> </TD> - <% pkg_datestr($pkg, 'next_bill',$conf) %> + <% pkg_datestr($cust_pkg, 'bill', $conf) %> </TR> % } -% if ( $pkg->{'expire'} ) { +% if ( $cust_pkg->get('expire') ) { <TR> <TD WIDTH="<%$width%>" ALIGN="right">Expires </TD> - <% pkg_datestr($pkg, 'expire',$conf) %> + <% pkg_datestr($cust_pkg, 'expire', $conf) %> </TR> % } -% if ( $pkg->{freq} ) { +% if ( $part_pkg->freq ) { <TR> <TD COLSPAN=<%$colspan%>> <FONT SIZE=-1> % if ( $curuser->access_right('Suspend customer package') ) { - ( <% pkg_suspend_link($pkg) %> ) + ( <% pkg_suspend_link($cust_pkg) %> ) % } % if ( $curuser->access_right('Cancel customer package immediately') ) { - ( <% pkg_cancel_link($pkg) %> ) + ( <% pkg_cancel_link($cust_pkg) %> ) % } % if ( $curuser->access_right('Cancel customer package later') ) { - ( <% pkg_expire_link($pkg) %> ) + ( <% pkg_expire_link($cust_pkg) %> ) % } <FONT> @@ -338,46 +334,49 @@ Current packages <TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%"> -% foreach my $svcpart (sort {$a->{svcpart} <=> $b->{svcpart}} @{$pkg->{svcparts}}) { +% #foreach my $svcpart (sort {$a->{svcpart} <=> $b->{svcpart}} @{$pkg->{svcparts}}) { +% foreach my $part_svc ( $cust_pkg->part_svc ) { -% foreach my $service (@{$svcpart->{services}}) { +% #foreach my $service (@{$svcpart->{services}}) { +% foreach my $cust_svc ( @{ $part_svc->cust_pkg_svc } ) { <TR> - <TD ALIGN="right" VALIGN="top"><%svc_link($svcpart,$service)%></TD> - <TD STYLE="padding-bottom:0px"><B><%svc_label_link($svcpart,$service)%></B></TD> + <TD ALIGN="right" VALIGN="top"><% FS::UI::Web::svc_link($m, $part_svc, $cust_svc) %></TD> + <TD STYLE="padding-bottom:0px"><B><% FS::UI::Web::svc_label_link($m, $part_svc, $cust_svc) %></B></TD> </TR> - <TR> - <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px"><FONT SIZE="-2"> - -% if ( $curuser->access_right('Recharge customer service') -% && ($svcpart->{'svcdb'} eq 'svc_acct') -% && ($service->{seconds} ne '' -% || $service->{upbytes} ne '' -% || $service->{downbytes} ne '' -% || $service->{totalbytes} ne '' ) + <TR> + <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px"><FONT SIZE="-2"> + +% if ( $curuser->access_right('Recharge customer service') +% && $cust_svc->svcdb eq 'svc_acct' +% && ( $cust_svc->svc_x->seconds ne '' +% || $cust_svc->svc_x->upbytes ne '' +% || $cust_svc->svc_x->downbytes ne '' +% || $cust_svc->svc_x->totalbytes ne '' +% ) % ) { - ( <%svc_recharge_link($service)%> ) -% } + ( <%svc_recharge_link($cust_svc)%> ) +% } </FONT></TD> <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px"><FONT SIZE="-2"> -% if ( $curuser->access_right('Unprovision customer service') ) { - ( <%svc_unprovision_link($service)%> ) -% } +% if ( $curuser->access_right('Unprovision customer service') ) { + ( <%svc_unprovision_link($cust_svc)%> ) +% } </FONT></TD> </TR> % } -% if ( ! $pkg->{'cancel'} +% if ( ! $cust_pkg->get('cancel') % && $curuser->access_right('Provision customer service') -% && $svcpart->{count} < $svcpart->{quantity} +% && $part_svc->num_avail % ) { <TR> <TD COLSPAN=2 ALIGN="center" STYLE="padding-bottom:4px;padding-top:0px"> - <B><% svc_provision_link($pkg, $svcpart, $conf, $curuser) %></B> + <B><% svc_provision_link($cust_pkg, $part_svc, $conf, $curuser) %></B> </TD> </TR> @@ -414,104 +413,33 @@ Current packages % } else { % $method = 'all_pkgs'; % } -% -% foreach my $cust_pkg ( $cust_main->$method() ) { -% -% my $part_pkg = $cust_pkg->part_pkg; -% -% my %pkg = (); -% -% #to get back to the original object... should use it in the first place!! -% $pkg{cust_pkg} = $cust_pkg; -% $pkg{part_pkg} = $part_pkg; -% -% $pkg{pkgnum} = $cust_pkg->pkgnum; -% $pkg{pkg} = $part_pkg->pkg; -% $pkg{pkgpart} = $part_pkg->pkgpart; -% $pkg{comment} = $part_pkg->getfield('comment'); -% $pkg{freq} = $part_pkg->freq; -% $pkg{setup} = $cust_pkg->getfield('setup'); -% $pkg{last_bill} = $cust_pkg->getfield('last_bill'); -% $pkg{next_bill} = $cust_pkg->getfield('bill'); -% $pkg{susp} = $cust_pkg->getfield('susp'); -% $pkg{expire} = $cust_pkg->getfield('expire'); -% $pkg{cancel} = $cust_pkg->getfield('cancel'); -% $pkg{reason} = $cust_pkg->last_reason->reason if $cust_pkg->last_reason; -% -% -% my %svcparts = map { -% $_->svcpart => { -% $_->part_svc->hash, -% 'quantity' => $_->quantity, -% 'count' => $cust_pkg->num_cust_svc($_->svcpart), -% #'services' => [], -% }; -% } $part_pkg->pkg_svc; -% -% foreach my $cust_svc ( $cust_pkg->cust_svc ) { -% #warn "svcnum ". $cust_svc->svcnum. " / svcpart ". $cust_svc->svcpart. "\n"; -% my $svc = { -% 'svcnum' => $cust_svc->svcnum, -% 'label' => ($cust_svc->label)[1], -% $cust_svc->svc_x->hash, -% }; -% -% #false laziness with above, to catch extraneous services. whole -% #damn thing should be OO... -% my $svcpart = ( $svcparts{$cust_svc->svcpart} ||= { -% $cust_svc->part_svc->hash, -% 'quantity' => 0, -% 'count' => $cust_pkg->num_cust_svc($cust_svc->svcpart), -% #'services' => [], -% } ); -% -% push @{$svcpart->{services}}, $svc; -% -% } -% -% $pkg{svcparts} = [ values %svcparts ]; -% -% push @packages, \%pkg; -% -% } -% -% return \@packages; % +% [ $cust_main->$method() ]; %} -% -%sub svc_link { -% -% my ($svcpart, $svc) = (shift,shift) or return ''; -% return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svcpart->{svc}</A>!; -% -%} -% -%sub svc_label_link { -% -% my ($svcpart, $svc) = (shift,shift) or return ''; -% return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svc->{label}</A>!; -% -%} -% +% %sub svc_provision_link { -% my ($pkg, $svcpart, $conf, $curuser) = @_; -% ( my $svc_nbsp = $svcpart->{svc} ) =~ s/\s+/ /g; -% my $num_left = $svcpart->{quantity} - $svcpart->{count}; -% my $pkgnum_svcpart = "pkgnum$pkg->{pkgnum}-svcpart$svcpart->{svcpart}"; -% +% my ($cust_pkg, $part_svc, $conf, $curuser) = @_; +% ( my $svc_nbsp = $part_svc->svc ) =~ s/\s+/ /g; +% my $num_avail = $part_svc->num_avail; +% my $pkgnum_svcpart = "pkgnum=". $cust_pkg->pkgnum. ';'. +% "svcpart=". $part_svc->svcpart; % my $url; -% if ( $svcpart->{svcdb} eq 'svc_external' +% if ( $part_svc->svcdb eq 'svc_external' #could be generalized % && $conf->exists('svc_external-skip_manual') % ) { -% $url = "${p}edit/process/$svcpart->{svcdb}.cgi?". -% "pkgnum=$pkg->{pkgnum}&". -% "svcpart=$svcpart->{svcpart}"; +% $url = "${p}edit/process/". $part_svc->svcdb. ".cgi?$pkgnum_svcpart"; % } else { -% $url = "${p}edit/$svcpart->{svcdb}.cgi?$pkgnum_svcpart"; +% $url = FS::UI::Web::svc_url( +% 'm' => $m, +% 'action' => 'edit', +% 'part_svc' => $part_svc, +% 'query' => $pkgnum_svcpart, +% ); +% #$url = "${p}edit/$svcpart->{svcdb}.cgi?$pkgnum_svcpart"; % } % % my $link = qq!<A CLASS="provision" HREF="$url">!. -% "Provision $svc_nbsp ($num_left)</A>"; +% "Provision $svc_nbsp ($num_avail)</A>"; % if ( $conf->exists('legacy_link') % && $curuser->access_right('View/link unlinked services') % ) @@ -519,39 +447,20 @@ Current packages % $link .= '<BR>'. % qq!<A CLASS="provision" HREF="${p}misc/link.cgi?!. % qq!$pkgnum_svcpart">!. -% "Link to legacy $svc_nbsp ($num_left)</A>"; +% "Link to legacy $svc_nbsp ($num_avail)</A>"; % } % $link; %} % %sub svc_unprovision_link { -% my $svc = shift or return ''; -% qq!<A HREF="javascript:areyousure('${p}misc/unprovision.cgi?$svc->{svcnum}',!. -% qq!'Permanently unprovision and delete this service?')">Unprovision</A>!; -%} -% -%sub svc_recharge_link { -% my $svc = shift or return ''; -% -% qq!<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}misc/recharge_svc.html?svcnum=$svc->{svcnum}', 392, 336, 'recharge_svc_popup' ), CAPTION, 'Recharge service $svc->{svcnum}', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Recharge</A>!; -%} -% -%# This should be generalized to use config options to determine order. -%sub pkgsort_pkgnum_cancel { -% if ($a->{cancel} and $b->{cancel}) { -% return ($a->{pkgnum} <=> $b->{pkgnum}); -% } elsif ($a->{cancel} or $b->{cancel}) { -% return (-1) if ($b->{cancel}); -% return (1) if ($a->{cancel}); -% return (0); -% } else { -% return($a->{pkgnum} <=> $b->{pkgnum}); -% } +% my $cust_svc = shift or return ''; +% qq!<A HREF="javascript:areyousure('${p}misc/unprovision.cgi?!. $cust_svc->svcnum. +% qq!', 'Permanently unprovision and delete this service?')">Unprovision</A>!; %} % %sub pkg_datestr { -% my($pkg, $field, $conf) = @_ or return ''; -% return ' ' unless $pkg->{$field}; +% my($cust_pkg, $field, $conf) = @_ or return ''; +% return ' ' unless $cust_pkg->get($field); % my $format = '<TD align="left"><B>%b</B></TD>'. % '<TD align="right"><B> %o,</B></TD>'. % '<TD align="right"><B> %Y</B></TD>'; @@ -561,48 +470,68 @@ Current packages % '<TD ALIGN="left"><B>%M</B></TD>'. % '<TD ALIGN="left"><B> %P</B></TD>' % if $conf->exists('cust_pkg-display_times'); -% ( my $strip = time2str($format, $pkg->{$field}) ) =~ s/ (\d)/$1/g; +% my $strip = time2str($format, $cust_pkg->get($field) ); +% $strip =~ s/ (\d)/$1/g; % $strip; %} % -%sub pkg_change_link { -% my $pkg = shift or return ''; -% return qq!<a href="${p}misc/change_pkg.cgi?$pkg->{pkgnum}">!. -% qq!Change package</a>!; +%sub pkg_change_link { pkg_link('misc/change_pkg', 'Change package', @_ ); } +%sub pkg_suspend_link { pkg_link('misc/susp_pkg', 'Suspend', @_ ); } +%sub pkg_unsuspend_link { pkg_link('misc/unsusp_pkg', 'Unsuspend', @_ ); } +%sub pkg_expire_link { pkg_link('misc/expire_pkg', 'Cancel later', @_ ); } +%sub pkg_dates_link { pkg_link('edit/REAL_cust_pkg', 'Edit dates', @_ ); } +% +%sub pkg_cancel_link { pkg_popup_link( 'misc/cancel_pkg.html?method=cancel', +% 'Cancel now', +% 'Cancel', +% @_ +% ); +% } +%sub pkg_expire_link { pkg_popup_link( 'misc/cancel_pkg.html?method=expire', +% 'Cancel later', +% 'Expire', #"Cancel package $num later" +% @_ +% ); +% } +% +%sub svc_recharge_link { svc_popup_link( 'misc/recharge_svc.html', +% 'Recharge', +% 'Recharge', +% @_ +% ); +% } +% +%sub pkg_link { +% my($action, $label, $cust_pkg) = @_; +% return '' unless $cust_pkg; +% qq!<a href="${p}misc/$action.cgi?!. $cust_pkg->pkgnum. qq!">$label</a>!; %} % -%sub pkg_suspend_link { -% my $pkg = shift or return ''; -% qq!<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}misc/cancel_pkg.html?method=suspend&pkgnum=$pkg->{pkgnum}', 392, 336, 'suspend_pkg_popup' ), CAPTION, 'Suspend package $pkg->{pkgnum}', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Suspend</A>!; +%sub pkg_popup_link { +% my($action, $label, $actionlabel, $cust_pkg) = @_; +% $action .= '&pkgnum='. $cust_pkg->pkgnum; +% $actionlabel .= ' package '. $cust_pkg->pkgnum; +% popup_link($action, $label, $actionlabel); %} % -%sub pkg_unsuspend_link { -% my $pkg = shift or return ''; -% return qq!<a href="${p}misc/unsusp_pkg.cgi?$pkg->{pkgnum}">Unsuspend</a>!; +%sub svc_popup_link { +% my($action, $label, $actionlabel, $cust_svc) = @_; +% $action .= '?svcnum='. $cust_svc->svcnum; +% $actionlabel .= ' service '. $cust_svc->svcnum; +% popup_link($action, $label, $actionlabel); %} % -%sub pkg_cancel_link { -% my $pkg = shift or return ''; -% -% qq!<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}misc/cancel_pkg.html?method=cancel&pkgnum=$pkg->{pkgnum}', 392, 336, 'cancel_pkg_popup' ), CAPTION, 'Cancel package $pkg->{pkgnum}', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Cancel now</A>!; -%} -% -%sub pkg_expire_link { -% my $pkg = shift or return ''; -% qq!<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}misc/cancel_pkg.html?method=expire&pkgnum=$pkg->{pkgnum}', 392, 336, 'expire_pkg_popup' ), CAPTION, 'Expire package $pkg->{pkgnum}', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Cancel later</A>!; -%} -% -%sub pkg_dates_link { -% my $pkg = shift or return ''; -% qq!<A HREF="${p}edit/REAL_cust_pkg.cgi?$pkg->{pkgnum}">Edit dates</A>!; +%sub popup_link { +% my($action, $label, $actionlabel) = @_; +% qq!<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('$p$action', 392, 336, 'pkg_or_svc_action_popup' ), CAPTION, '$actionlabel', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">$label</A>!; %} % %sub pkg_customize_link { -% my $pkg = shift or return ''; -% my $custnum = shift; -% qq!<A HREF="${p}edit/part_pkg.cgi?keywords=$custnum;clone=$pkg->{pkgpart};!. -% qq!pkgnum=$pkg->{pkgnum}">Customize</A>!; +% my $cust_pkg = shift or return ''; +% my $custnum = $cust_pkg->custnum; +% qq!<A HREF="${p}edit/part_pkg.cgi?!. +% "keywords=$custnum;". +% "clone=". $cust_pkg->part_pkg->pkgpart. ';'. +% "pkgnum=". $cust_pkg->pkgnum. +% qq!">Customize</A>!; %} -% -% - diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html index 35434632e..7b8df3a74 100644 --- a/httemplate/view/elements/svc_Common.html +++ b/httemplate/view/elements/svc_Common.html @@ -12,6 +12,9 @@ % # if not specified all columns (except for the primary key) will be viewable % # 'fields' => [ % # ] +% # +% # # defaults to "edit/$table.cgi?", will have svcnum appended +% # 'edit_url' => % % my(%opt) = @_; % @@ -21,9 +24,15 @@ % #|| [ grep { $_ ne 'svcnum' } dbdef->table($table)->columns ]; % || [ grep { $_ ne 'svcnum' } fields($table) ]; % -% my($query) = $cgi->keywords; -% $query =~ /^(\d+)$/; -% my $svcnum = $1; +% my $svcnum; +% if ( $cgi->param('svcnum') ) { +% $cgi->param('svcnum') =~ /^(\d+)$/ or die "unparsable svcnum"; +% $svcnum = $1; +% } else { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ or die "no svcnum"; +% $svcnum = $1; +% } % my $svc_x = qsearchs( $opt{'table'}, { 'svcnum' => $svcnum } ) % or die "Unknown svcnum $svcnum in ". $opt{'table'}. " table\n"; % @@ -69,7 +78,8 @@ Service #<B><% $svcnum %></B> -| <A HREF="<%$p%>edit/<% $opt{'table'} %>.cgi?<%$svcnum%>">Edit this <% $label %></A> +% my $url = $opt{'edit_url'} || $p. 'edit/'. $opt{'table'}. '.cgi?'; +| <A HREF="<%$url%><%$svcnum%>">Edit this <% $label %></A> <BR> <% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %> diff --git a/rt/html/Elements/FreesideSearch b/rt/html/Elements/FreesideSearch index 99b8da072..2fed8fc73 100644 --- a/rt/html/Elements/FreesideSearch +++ b/rt/html/Elements/FreesideSearch @@ -6,6 +6,6 @@ } </SCRIPT> <input name="search_cust" accesskey="0" VALUE="(cust #, name, company or phone)" SIZE="28" onFocus="clearhint_search_cust(this);" onClick="clearhint_search_cust(this);" STYLE="text-align:right; font-family: Arial, Verdana, Helvetica, sans-serif;"><BR> -<A HREF="<% $RT::URI::freeside::URL %>/search/cust_main.html" STYLE="color: #000000; font-size: 70%; font-weight:normal">Advanced</A> +<A NOTYET="<% $RT::URI::freeside::URL %>/search/cust_main.html" STYLE="color: #000000; font-size: 70%; font-weight:normal">Advanced</A> <input type="submit" value="<&|/l&>Search customers</&>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%"> </form> diff --git a/rt/html/Elements/FreesideSvcSearch b/rt/html/Elements/FreesideSvcSearch index e9ad56426..4a5942421 100644 --- a/rt/html/Elements/FreesideSvcSearch +++ b/rt/html/Elements/FreesideSvcSearch @@ -1,4 +1,4 @@ -<form action="<% $RT::URI::freeside::URL %>/search/svc_Smart.html" STYLE="margin:0"> +<form action="<% $RT::URI::freeside::URL %>/search/cust_svc.html" STYLE="margin:0"> <SCRIPT TYPE="text/javascript"> function clearhint_search_svc (what) { if ( what.value == '(user, user@domain or domain)' ) @@ -6,6 +6,6 @@ } </SCRIPT> <input name="search_svc" accesskey="0" VALUE="(user, user@domain or domain)" SIZE="26" onFocus="clearhint_search_svc(this);" onClick="clearhint_search_svc(this);" STYLE="text-align:right; font-family: Arial, Verdana, Helvetica, sans-serif;"><BR> - <A HREF="<% $RT::URI::freeside::URL %>search/svc_Smarter.html" STYLE="color: #000000; font-size: 70%; font-weight:normal">Advanced</A> + <A NOTYET="<% $RT::URI::freeside::URL %>search/svc_Smarter.html" STYLE="color: #000000; font-size: 70%; font-weight:normal">Advanced</A> <input type="submit" value="<&|/l&>Search services</&>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%"> </form>