summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FS/FS/Record.pm340
-rwxr-xr-xFS/FS/addr_block.pm2
-rw-r--r--FS/FS/agent.pm5
-rw-r--r--FS/FS/agent_type.pm5
-rw-r--r--FS/FS/cust_bill.pm2
-rw-r--r--FS/FS/cust_bill_event.pm2
-rw-r--r--FS/FS/cust_bill_pay.pm4
-rw-r--r--FS/FS/cust_bill_pkg.pm2
-rw-r--r--FS/FS/cust_bill_pkg_detail.pm3
-rw-r--r--FS/FS/cust_credit.pm4
-rw-r--r--FS/FS/cust_credit_bill.pm4
-rw-r--r--FS/FS/cust_credit_refund.pm4
-rw-r--r--FS/FS/cust_main.pm2
-rw-r--r--FS/FS/cust_main_county.pm4
-rw-r--r--FS/FS/cust_main_invoice.pm4
-rw-r--r--FS/FS/cust_pay.pm5
-rw-r--r--FS/FS/cust_pay_batch.pm4
-rw-r--r--FS/FS/cust_pkg.pm2
-rw-r--r--FS/FS/cust_refund.pm4
-rw-r--r--FS/FS/cust_svc.pm2
-rw-r--r--FS/FS/cust_tax_exempt.pm1
-rw-r--r--FS/FS/domain_record.pm4
-rw-r--r--FS/FS/export_svc.pm1
-rw-r--r--FS/FS/msgcat.pm2
-rw-r--r--FS/FS/nas.pm6
-rw-r--r--FS/FS/part_bill_event.pm3
-rw-r--r--FS/FS/part_export.pm21
-rw-r--r--FS/FS/part_export_option.pm2
-rw-r--r--FS/FS/part_pkg.pm1
-rw-r--r--FS/FS/part_pop_local.pm3
-rw-r--r--FS/FS/part_referral.pm1
-rw-r--r--FS/FS/part_svc.pm8
-rw-r--r--FS/FS/part_svc_column.pm8
-rwxr-xr-xFS/FS/part_virtual_field.pm303
-rw-r--r--FS/FS/pkg_svc.pm4
-rw-r--r--FS/FS/port.pm4
-rw-r--r--FS/FS/prepay_credit.pm1
-rw-r--r--FS/FS/queue.pm4
-rw-r--r--FS/FS/queue_arg.pm4
-rw-r--r--FS/FS/queue_depend.pm1
-rw-r--r--FS/FS/radius_usergroup.pm1
-rwxr-xr-xFS/FS/router.pm16
-rw-r--r--FS/FS/session.pm4
-rw-r--r--FS/FS/svc_Common.pm58
-rw-r--r--FS/FS/svc_acct.pm2
-rw-r--r--FS/FS/svc_acct_pop.pm3
-rwxr-xr-xFS/FS/svc_broadband.pm59
-rw-r--r--FS/FS/svc_domain.pm4
-rw-r--r--FS/FS/svc_forward.pm2
-rw-r--r--FS/FS/svc_www.pm3
-rw-r--r--FS/FS/type_pkgs.pm4
-rw-r--r--FS/MANIFEST5
-rwxr-xr-xFS/bin/freeside-setup77
-rw-r--r--htetc/global.asa5
-rw-r--r--htetc/handler.pl5
-rw-r--r--htetc/handler.pl-1.0x5
-rwxr-xr-xhttemplate/browse/part_svc.cgi10
-rw-r--r--httemplate/browse/part_virtual_field.cgi39
-rw-r--r--httemplate/browse/router.cgi25
-rw-r--r--httemplate/docs/upgrade10.html54
-rwxr-xr-xhttemplate/edit/part_svc.cgi72
-rw-r--r--httemplate/edit/part_virtual_field.cgi92
-rw-r--r--httemplate/edit/process/router.cgi41
-rw-r--r--httemplate/edit/process/svc_broadband.cgi36
-rwxr-xr-xhttemplate/edit/router.cgi42
-rwxr-xr-xhttemplate/edit/svc_acct.cgi12
-rw-r--r--httemplate/edit/svc_broadband.cgi34
-rw-r--r--httemplate/edit/svc_www.cgi12
-rw-r--r--httemplate/index.html8
-rwxr-xr-xhttemplate/view/svc_acct.cgi18
-rw-r--r--httemplate/view/svc_broadband.cgi24
-rwxr-xr-xhttemplate/view/svc_forward.cgi9
-rw-r--r--httemplate/view/svc_www.cgi11
73 files changed, 1102 insertions, 476 deletions
diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm
index 02fd4e390..9d82d949b 100644
--- a/FS/FS/Record.pm
+++ b/FS/FS/Record.pm
@@ -14,10 +14,14 @@ use FS::UID qw(dbh getotaker datasrc driver_name);
use FS::SearchCache;
use FS::Msgcat qw(gettext);
+use FS::part_virtual_field;
+
+use Tie::IxHash;
+
@ISA = qw(Exporter);
@EXPORT_OK = qw(dbh fields hfields qsearch qsearchs dbdef jsearch);
-$DEBUG = 0;
+$DEBUG = 2;
$me = '[FS::Record]';
#ask FS::UID to run this stuff for us later
@@ -200,12 +204,15 @@ sub qsearch {
my $dbh = dbh;
my $table = $cache ? $cache->table : $stable;
+ my $pkey = $dbdef->table($table)->primary_key;
- my @fields = grep exists($record->{$_}), fields($table);
+ my @real_fields = grep exists($record->{$_}), real_fields($table);
+ my @virtual_fields = grep exists($record->{$_}), "FS::$table"->virtual_fields;
my $statement = "SELECT $select FROM $stable";
- if ( @fields ) {
- $statement .= ' WHERE '. join(' AND ', map {
+ if ( @real_fields or @virtual_fields ) {
+ $statement .= ' WHERE '. join(' AND ',
+ ( map {
my $op = '=';
my $column = $_;
@@ -251,8 +258,45 @@ sub qsearch {
} else {
"$column $op ?";
}
- } @fields );
+ } @real_fields ),
+ ( map {
+ my $op = '=';
+ my $column = $_;
+ if ( ref($record->{$_}) ) {
+ $op = $record->{$_}{'op'} if $record->{$_}{'op'};
+ if ( uc($op) eq 'ILIKE' ) {
+ $op = 'LIKE';
+ $record->{$_}{'value'} = lc($record->{$_}{'value'});
+ $column = "LOWER($_)";
+ }
+ $record->{$_} = $record->{$_}{'value'};
+ }
+
+ # ... EXISTS ( SELECT name, value FROM part_virtual_field
+ # JOIN virtual_field
+ # ON part_virtual_field.vfieldpart = virtual_field.vfieldpart
+ # WHERE recnum = svc_acct.svcnum
+ # AND (name, value) = ('egad', 'brain') )
+
+ my $value = $record->{$_};
+
+ my $subq;
+
+ $subq = ($value ? 'EXISTS ' : 'NOT EXISTS ') .
+ "( SELECT part_virtual_field.name, virtual_field.value ".
+ "FROM part_virtual_field JOIN virtual_field ".
+ "ON part_virtual_field.vfieldpart = virtual_field.vfieldpart ".
+ "WHERE virtual_field.recnum = ${table}.${pkey} ".
+ "AND part_virtual_field.name = '${column}'".
+ ($value ?
+ " AND virtual_field.value ${op} '${value}'"
+ : "") . ")";
+ $subq;
+
+ } @virtual_fields ) );
+
}
+
$statement .= " $extra_sql" if defined($extra_sql);
warn "[debug]$me $statement\n" if $DEBUG > 1;
@@ -262,7 +306,7 @@ sub qsearch {
my $bind = 1;
foreach my $field (
- grep defined( $record->{$_} ) && $record->{$_} ne '', @fields
+ grep defined( $record->{$_} ) && $record->{$_} ne '', @real_fields
) {
if ( $record->{$field} =~ /^\d+(\.\d+)?$/
&& $dbdef->table($table)->column($field)->type =~ /(int)/i
@@ -279,31 +323,64 @@ sub qsearch {
$sth->execute or croak "Error executing \"$statement\": ". $sth->errstr;
- $dbh->commit or croak $dbh->errstr if $FS::UID::AutoCommit;
+ my %result;
+ tie %result, "Tie::IxHash";
+ @virtual_fields = "FS::$table"->virtual_fields;
+
+ my @stuff = @{ $sth->fetchall_arrayref( {} ) };
+ if($pkey) {
+ %result = map { $_->{$pkey}, $_ } @stuff;
+ } else {
+ @result{@stuff} = @stuff;
+ }
+ $sth->finish;
+ if ( keys(%result) and @virtual_fields ) {
+ $statement =
+ "SELECT virtual_field.recnum, part_virtual_field.name, ".
+ "virtual_field.value ".
+ "FROM part_virtual_field JOIN virtual_field USING (vfieldpart) ".
+ "WHERE part_virtual_field.dbtable = '$table' AND ".
+ "virtual_field.recnum IN (".
+ join(',', keys(%result)). ") AND part_virtual_field.name IN ('".
+ join(q!', '!, @virtual_fields) . "')";
+ warn "[debug]$me $statement\n" if $DEBUG > 1;
+ $sth = $dbh->prepare($statement) or croak "$dbh->errstr doing $statement";
+ $sth->execute or croak "Error executing \"$statement\": ". $sth->errstr;
+
+ foreach (@{ $sth->fetchall_arrayref({}) }) {
+ my $recnum = $_->{recnum};
+ my $name = $_->{name};
+ my $value = $_->{value};
+ if (exists($result{$recnum})) {
+ $result{$recnum}->{$name} = $value;
+ }
+ }
+ }
+
if ( eval 'scalar(@FS::'. $table. '::ISA);' ) {
if ( eval 'FS::'. $table. '->can(\'new\')' eq \&new ) {
#derivied class didn't override new method, so this optimization is safe
if ( $cache ) {
map {
new_or_cached( "FS::$table", { %{$_} }, $cache )
- } @{$sth->fetchall_arrayref( {} )};
+ } values(%result);
} else {
map {
new( "FS::$table", { %{$_} } )
- } @{$sth->fetchall_arrayref( {} )};
+ } values(%result);
}
} else {
warn "untested code (class FS::$table uses custom new method)";
map {
eval 'FS::'. $table. '->new( { %{$_} } )';
- } @{$sth->fetchall_arrayref( {} )};
+ } values(%result);
}
} else {
cluck "warning: FS::$table not loaded; returning FS::Record objects";
map {
FS::Record->new( $table, { %{$_} } );
- } @{$sth->fetchall_arrayref( {} )};
+ } values(%result);
}
}
@@ -512,15 +589,15 @@ sub insert {
my $table = $self->table;
#false laziness w/delete
- my @fields =
+ my @real_fields =
grep defined($self->getfield($_)) && $self->getfield($_) ne "",
- $self->fields
+ real_fields($table)
;
- my @values = map { _quote( $self->getfield($_), $table, $_) } @fields;
+ my @values = map { _quote( $self->getfield($_), $table, $_) } @real_fields;
#eslaf
my $statement = "INSERT INTO $table ( ".
- join( ', ', @fields ).
+ join( ', ', @real_fields ).
") VALUES (".
join( ', ', @values ).
")"
@@ -537,9 +614,9 @@ sub insert {
$sth->execute or return $sth->errstr;
+ my $insertid = '';
if ( $db_seq ) { # get inserted id from the database, if applicable
warn "[debug]$me retreiving sequence from database\n" if $DEBUG;
- my $insertid = '';
if ( driver_name eq 'Pg' ) {
my $oid = $sth->{'pg_oid_status'};
@@ -581,6 +658,34 @@ sub insert {
$self->setfield($primary_key, $insertid);
}
+ my @virtual_fields =
+ grep defined($self->getfield($_)) && $self->getfield($_) ne "",
+ $self->virtual_fields;
+ if (@virtual_fields) {
+ my %v_values = map { $_, $self->getfield($_) } @virtual_fields;
+
+ my $vfieldpart = vfieldpart_hashref($table);
+
+ my $v_statement = "INSERT INTO virtual_field(recnum, vfieldpart, value) ".
+ "VALUES (?, ?, ?)";
+
+ my $v_sth = dbh->prepare($v_statement) or do {
+ dbh->rollback if $FS::UID::AutoCommit;
+ return dbh->errstr;
+ };
+
+ foreach (keys(%v_values)) {
+ $v_sth->execute($self->getfield($primary_key),
+ $vfieldpart->{$_},
+ $v_values{$_})
+ or do {
+ dbh->rollback if $FS::UID::AutoCommit;
+ return $v_sth->errstr;
+ };
+ }
+ }
+
+
my $h_sth;
if ( defined $dbdef->table('h_'. $table) ) {
my $h_statement = $self->_h_statement('insert');
@@ -631,7 +736,7 @@ sub delete {
: "$_ = ". _quote($self->getfield($_),$self->table,$_)
} ( $self->dbdef_table->primary_key )
? ( $self->dbdef_table->primary_key)
- : $self->fields
+ : real_fields($self->table)
);
warn "[debug]$me $statement\n" if $DEBUG > 1;
my $sth = dbh->prepare($statement) or return dbh->errstr;
@@ -645,6 +750,19 @@ sub delete {
$h_sth = '';
}
+ my $primary_key = $self->dbdef_table->primary_key;
+ my $v_sth;
+ my @del_vfields;
+ my $vfp = vfieldpart_hashref($self->table);
+ foreach($self->virtual_fields) {
+ next if $self->getfield($_) eq '';
+ unless(@del_vfields) {
+ my $st = "DELETE FROM virtual_field WHERE recnum = ? AND vfieldpart = ?";
+ $v_sth = dbh->prepare($st) or return dbh->errstr;
+ }
+ push @del_vfields, $_;
+ }
+
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
local $SIG{QUIT} = 'IGNORE';
@@ -655,6 +773,10 @@ sub delete {
my $rc = $sth->execute or return $sth->errstr;
#not portable #return "Record not found, statement:\n$statement" if $rc eq "0E0";
$h_sth->execute or return $h_sth->errstr if $h_sth;
+ $v_sth->execute($self->getfield($primary_key), $vfp->{$_})
+ or return $v_sth->errstr
+ foreach (@del_vfields);
+
dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit;
#no need to needlessly destoy the data either (causes problems actually)
@@ -695,8 +817,11 @@ sub replace {
my $error = $new->check;
return $error if $error;
- my @diff = grep $new->getfield($_) ne $old->getfield($_), $old->fields;
- unless ( @diff ) {
+ #my @diff = grep $new->getfield($_) ne $old->getfield($_), $old->fields;
+ my %diff = map { ($new->getfield($_) ne $old->getfield($_))
+ ? ($_, $new->getfield($_)) : () } $old->fields;
+
+ unless ( keys(%diff) ) {
carp "[warning]$me $new -> replace $old: records identical";
return '';
}
@@ -704,7 +829,7 @@ sub replace {
my $statement = "UPDATE ". $old->table. " SET ". join(', ',
map {
"$_ = ". _quote($new->getfield($_),$old->table,$_)
- } @diff
+ } real_fields($old->table)
). ' WHERE '.
join(' AND ',
map {
@@ -715,7 +840,7 @@ sub replace {
: "( $_ IS NULL OR $_ = \"\" )"
)
: "$_ = ". _quote($old->getfield($_),$old->table,$_)
- } ( $primary_key ? ( $primary_key ) : $old->fields )
+ } ( $primary_key ? ( $primary_key ) : real_fields($old->table) )
)
;
warn "[debug]$me $statement\n" if $DEBUG > 1;
@@ -739,6 +864,44 @@ sub replace {
$h_new_sth = '';
}
+ # For virtual fields we have three cases with different SQL
+ # statements: add, replace, delete
+ my $v_add_sth;
+ my $v_rep_sth;
+ my $v_del_sth;
+ my (@add_vfields, @rep_vfields, @del_vfields);
+ my $vfp = vfieldpart_hashref($old->table);
+ foreach(grep { exists($diff{$_}) } $new->virtual_fields) {
+ if($diff{$_} eq '') {
+ # Delete
+ unless(@del_vfields) {
+ my $st = "DELETE FROM virtual_field WHERE recnum = ? ".
+ "AND vfieldpart = ?";
+ warn "[debug]$me $st\n" if $DEBUG > 2;
+ $v_del_sth = dbh->prepare($st) or return dbh->errstr;
+ }
+ push @del_vfields, $_;
+ } elsif($old->getfield($_) eq '') {
+ # Add
+ unless(@add_vfields) {
+ my $st = "INSERT INTO virtual_field (value, recnum, vfieldpart) ".
+ "VALUES (?, ?, ?)";
+ warn "[debug]$me $st\n" if $DEBUG > 2;
+ $v_add_sth = dbh->prepare($st) or return dbh->errstr;
+ }
+ push @add_vfields, $_;
+ } else {
+ # Replace
+ unless(@rep_vfields) {
+ my $st = "UPDATE virtual_field SET value = ? ".
+ "WHERE recnum = ? AND vfieldpart = ?";
+ warn "[debug]$me $st\n" if $DEBUG > 2;
+ $v_rep_sth = dbh->prepare($st) or return dbh->errstr;
+ }
+ push @rep_vfields, $_;
+ }
+ }
+
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
local $SIG{QUIT} = 'IGNORE';
@@ -750,6 +913,24 @@ sub replace {
#not portable #return "Record not found (or records identical)." if $rc eq "0E0";
$h_old_sth->execute or return $h_old_sth->errstr if $h_old_sth;
$h_new_sth->execute or return $h_new_sth->errstr if $h_new_sth;
+
+ $v_del_sth->execute($old->getfield($primary_key),
+ $vfp->{$_})
+ or return $v_del_sth->errstr
+ foreach(@del_vfields);
+
+ $v_add_sth->execute($new->getfield($_),
+ $old->getfield($primary_key),
+ $vfp->{$_})
+ or return $v_add_sth->errstr
+ foreach(@add_vfields);
+
+ $v_rep_sth->execute($new->getfield($_),
+ $old->getfield($primary_key),
+ $vfp->{$_})
+ or return $v_rep_sth->errstr
+ foreach(@rep_vfields);
+
dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit;
'';
@@ -769,12 +950,28 @@ sub rep {
=item check
-Not yet implemented, croaks. Derived classes should provide a check method.
+Checks virtual fields (using check_blocks). Subclasses should still provide
+a check method to validate real fields, foreign keys, etc., and call this
+method via $self->SUPER::check.
+
+(FIXME: Should this method try to make sure that it I<is> being called from
+a subclass's check method, to keep the current semantics as far as possible?)
=cut
sub check {
- confess "FS::Record::check not implemented; supply one in subclass!";
+ #confess "FS::Record::check not implemented; supply one in subclass!";
+ my $self = shift;
+
+ foreach my $field ($self->virtual_fields) {
+ for ($self->getfield($field)) {
+ # See notes on check_block in FS::part_virtual_field.
+ eval $self->pvf($field)->check_block;
+ return $@ if $@;
+ $self->setfield($field, $_);
+ }
+ }
+ '';
}
sub _h_statement {
@@ -782,7 +979,7 @@ sub _h_statement {
my @fields =
grep defined($self->getfield($_)) && $self->getfield($_) ne "",
- $self->fields
+ real_fields($self->table);
;
my @values = map { _quote( $self->getfield($_), $self->table, $_) } @fields;
@@ -1163,36 +1360,89 @@ sub ut_foreign_keyn {
: '';
}
+
+=item virtual_fields [ TABLE ]
+
+Returns a list of virtual fields defined for the table. This should not
+be exported, and should only be called as an instance or class method.
+
+=cut
+
+sub virtual_fields {
+ my $something = shift;
+ my $table;
+ $table = $something->table or confess "virtual_fields called on non-table";
+
+ confess "Unknown table $table" unless $dbdef->table($table);
+
+ # This should be smart enough to cache results.
+
+ my $query = 'SELECT name from part_virtual_field ' .
+ "WHERE dbtable = '$table'";
+ my $dbh = dbh;
+ my $result = $dbh->selectcol_arrayref($query);
+ confess $dbh->errstr if $dbh->err;
+ return @$result;
+}
+
+
=item fields [ TABLE ]
-This can be used as both a subroutine and a method call. It returns a list
-of the columns in this record's table, or an explicitly specified table.
-(See L<DBIx::DBSchema::Table>).
+This is a wrapper for real_fields and virtual_fields. Code that called
+fields before should probably continue to call fields.
=cut
-# Usage: @fields = fields($table);
-# @fields = $record->fields;
sub fields {
my $something = shift;
my $table;
- if ( ref($something) ) {
+ if($something->isa('FS::Record')) {
$table = $something->table;
} else {
$table = $something;
+ $something = "FS::$table";
}
- #croak "Usage: \@fields = fields(\$table)\n or: \@fields = \$record->fields" unless $table;
- my($table_obj) = $dbdef->table($table);
- confess "Unknown table $table" unless $table_obj;
- $table_obj->columns;
+ return (real_fields($table), $something->virtual_fields());
}
=back
+=item pvf FIELD_NAME
+
+Returns the FS::part_virtual_field object corresponding to a field in the
+record (specified by FIELD_NAME).
+
+=cut
+
+sub pvf {
+ my ($self, $name) = (shift, shift);
+
+ if(grep /^$name$/, $self->virtual_fields) {
+ return qsearchs('part_virtual_field', { dbtable => $self->table,
+ name => $name } );
+ }
+ ''
+}
+
=head1 SUBROUTINES
=over 4
+=item real_fields [ TABLE ]
+
+Returns a list of the real columns in the specified table. Called only by
+fields() and other subroutines elsewhere in FS::Record.
+
+=cut
+
+sub real_fields {
+ my $table = shift;
+
+ my($table_obj) = $dbdef->table($table);
+ confess "Unknown table $table" unless $table_obj;
+ $table_obj->columns;
+}
+
=item reload_dbdef([FILENAME])
Load a database definition (see L<DBIx::DBSchema>), optionally from a
@@ -1251,6 +1501,28 @@ sub _quote {
}
}
+=item vfieldpart_hashref TABLE
+
+Returns a hashref of virtual field names and vfieldparts applicable to the given
+TABLE.
+
+=cut
+
+sub vfieldpart_hashref {
+ my ($table) = @_;
+
+ return () unless $table;
+ my $dbh = dbh;
+ my $statement = "SELECT vfieldpart, name FROM part_virtual_field WHERE ".
+ "dbtable = '$table'";
+ my $sth = $dbh->prepare($statement);
+ $sth->execute or croak "Execution of '$statement' failed: ".$dbh->errstr;
+ return { map { $_->{name}, $_->{vfieldpart} }
+ @{$sth->fetchall_arrayref({})} };
+
+}
+
+
=item hfields TABLE
This is deprecated. Don't use it.
diff --git a/FS/FS/addr_block.pm b/FS/FS/addr_block.pm
index c5ddca7d0..4b034ef0c 100755
--- a/FS/FS/addr_block.pm
+++ b/FS/FS/addr_block.pm
@@ -122,7 +122,7 @@ sub check {
}
}
- '';
+ $self->SUPER::check;
}
diff --git a/FS/FS/agent.pm b/FS/FS/agent.pm
index f11a28db9..6de15ae6f 100644
--- a/FS/FS/agent.pm
+++ b/FS/FS/agent.pm
@@ -113,8 +113,7 @@ sub check {
return "Unknown typenum!"
unless $self->agent_type;
- '';
-
+ $self->SUPER::check;
}
=item agent_type
@@ -145,7 +144,7 @@ sub pkgpart_hashref {
=head1 VERSION
-$Id: agent.pm,v 1.3 2002-03-24 18:23:47 ivan Exp $
+$Id: agent.pm,v 1.4 2003-08-05 00:20:40 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/agent_type.pm b/FS/FS/agent_type.pm
index 988533ae3..5ba5ef291 100644
--- a/FS/FS/agent_type.pm
+++ b/FS/FS/agent_type.pm
@@ -102,7 +102,8 @@ sub check {
my $self = shift;
$self->ut_numbern('typenum')
- or $self->ut_text('atype');
+ or $self->ut_text('atype')
+ or $self->SUPER::check;
}
@@ -150,7 +151,7 @@ sub pkgpart {
=head1 VERSION
-$Id: agent_type.pm,v 1.1 1999-08-04 09:03:53 ivan Exp $
+$Id: agent_type.pm,v 1.2 2003-08-05 00:20:40 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm
index a22f44b24..4793608c0 100644
--- a/FS/FS/cust_bill.pm
+++ b/FS/FS/cust_bill.pm
@@ -161,7 +161,7 @@ sub check {
$self->printed(0) if $self->printed eq '';
- ''; #no error
+ $self->SUPER::check;
}
=item previous
diff --git a/FS/FS/cust_bill_event.pm b/FS/FS/cust_bill_event.pm
index c97734780..ddd676281 100644
--- a/FS/FS/cust_bill_event.pm
+++ b/FS/FS/cust_bill_event.pm
@@ -122,7 +122,7 @@ sub check {
return "Unknown eventpart ". $self->eventpart
unless qsearchs( 'part_bill_event' ,{ 'eventpart' => $self->eventpart } );
- ''; #no error
+ $self->SUPER::check;
}
=item part_bill_event
diff --git a/FS/FS/cust_bill_pay.pm b/FS/FS/cust_bill_pay.pm
index 913704bef..5f4a49144 100644
--- a/FS/FS/cust_bill_pay.pm
+++ b/FS/FS/cust_bill_pay.pm
@@ -170,7 +170,7 @@ sub check {
$self->_date(time) unless $self->_date;
- ''; #no error
+ $self->SUPER::check;
}
=item cust_pay
@@ -199,7 +199,7 @@ sub cust_bill {
=head1 VERSION
-$Id: cust_bill_pay.pm,v 1.12 2002-02-07 22:29:34 ivan Exp $
+$Id: cust_bill_pay.pm,v 1.13 2003-08-05 00:20:41 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm
index a6615d05d..6800707fe 100644
--- a/FS/FS/cust_bill_pkg.pm
+++ b/FS/FS/cust_bill_pkg.pm
@@ -171,7 +171,7 @@ sub check {
return "Unknown invnum"
unless qsearchs( 'cust_bill' ,{ 'invnum' => $self->invnum } );
- ''; #no error
+ $self->SUPER::check;
}
=item cust_pkg
diff --git a/FS/FS/cust_bill_pkg_detail.pm b/FS/FS/cust_bill_pkg_detail.pm
index 199de439b..261aa80ea 100644
--- a/FS/FS/cust_bill_pkg_detail.pm
+++ b/FS/FS/cust_bill_pkg_detail.pm
@@ -105,7 +105,8 @@ sub check {
|| $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum')
|| $self->ut_foreign_key('invnum', 'cust_pkg', 'invnum')
|| $self->ut_text('detail')
- ;
+ || $self->SUPER::check
+ ;
}
diff --git a/FS/FS/cust_credit.pm b/FS/FS/cust_credit.pm
index 284d59de2..e668abd73 100644
--- a/FS/FS/cust_credit.pm
+++ b/FS/FS/cust_credit.pm
@@ -174,7 +174,7 @@ sub check {
$self->otaker(getotaker);
- ''; #no error
+ $self->SUPER::check;
}
=item cust_refund
@@ -242,7 +242,7 @@ sub credited {
=head1 VERSION
-$Id: cust_credit.pm,v 1.16 2002-06-04 14:35:52 ivan Exp $
+$Id: cust_credit.pm,v 1.17 2003-08-05 00:20:41 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/cust_credit_bill.pm b/FS/FS/cust_credit_bill.pm
index 62215419c..a54acb683 100644
--- a/FS/FS/cust_credit_bill.pm
+++ b/FS/FS/cust_credit_bill.pm
@@ -127,7 +127,7 @@ sub check {
return "Cannot apply more than remaining value of invoice"
unless $self->amount <= $cust_bill->owed;
- ''; #no error
+ $self->SUPER::check;
}
=item sub cust_credit
@@ -145,7 +145,7 @@ sub cust_credit {
=head1 VERSION
-$Id: cust_credit_bill.pm,v 1.7 2002-01-24 16:58:47 ivan Exp $
+$Id: cust_credit_bill.pm,v 1.8 2003-08-05 00:20:41 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/cust_credit_refund.pm b/FS/FS/cust_credit_refund.pm
index cc3b32cdb..d0deae2f3 100644
--- a/FS/FS/cust_credit_refund.pm
+++ b/FS/FS/cust_credit_refund.pm
@@ -156,7 +156,7 @@ sub check {
return "unknown cust_credit.crednum: ". $self->crednum
unless qsearchs( 'cust_credit', { 'crednum' => $self->crednum } );
- ''; #no error
+ $self->SUPER::check;
}
=item cust_refund
@@ -185,7 +185,7 @@ sub cust_credit {
=head1 VERSION
-$Id: cust_credit_refund.pm,v 1.9 2002-01-26 01:52:31 ivan Exp $
+$Id: cust_credit_refund.pm,v 1.10 2003-08-05 00:20:41 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 51037ef97..2af2e98ce 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -798,7 +798,7 @@ sub check {
#warn "AFTER: \n". $self->_dump;
- ''; #no error
+ $self->SUPER::check;
}
=item all_pkgs
diff --git a/FS/FS/cust_main_county.pm b/FS/FS/cust_main_county.pm
index d8796e451..f631d8cae 100644
--- a/FS/FS/cust_main_county.pm
+++ b/FS/FS/cust_main_county.pm
@@ -113,7 +113,9 @@ sub check {
|| $self->ut_textn('taxclass') # ...
|| $self->ut_money('exempt_amount')
|| $self->ut_textn('taxname')
- ;
+ || $self->SUPER::check
+ ;
+
}
diff --git a/FS/FS/cust_main_invoice.pm b/FS/FS/cust_main_invoice.pm
index bcb1437af..add0ccab1 100644
--- a/FS/FS/cust_main_invoice.pm
+++ b/FS/FS/cust_main_invoice.pm
@@ -107,7 +107,7 @@ sub check {
return "Unknown customer"
unless qsearchs('cust_main',{ 'custnum' => $self->custnum });
- ''; #noerror
+ $self->SUPER::check;
}
=item checkdest
@@ -163,7 +163,7 @@ sub address {
=head1 VERSION
-$Id: cust_main_invoice.pm,v 1.13 2002-09-18 22:50:44 ivan Exp $
+$Id: cust_main_invoice.pm,v 1.14 2003-08-05 00:20:42 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm
index 55f2fc4a3..7be115356 100644
--- a/FS/FS/cust_pay.pm
+++ b/FS/FS/cust_pay.pm
@@ -354,8 +354,7 @@ sub check {
return $error if $error;
}
- ''; #no error
-
+ $self->SUPER::check;
}
=item cust_bill_pay
@@ -390,7 +389,7 @@ sub unapplied {
=head1 VERSION
-$Id: cust_pay.pm,v 1.24 2003-05-19 12:00:44 ivan Exp $
+$Id: cust_pay.pm,v 1.25 2003-08-05 00:20:42 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/cust_pay_batch.pm b/FS/FS/cust_pay_batch.pm
index c4427c387..1a530467c 100644
--- a/FS/FS/cust_pay_batch.pm
+++ b/FS/FS/cust_pay_batch.pm
@@ -185,14 +185,14 @@ sub check {
#check invnum, custnum, ?
- ''; #no error
+ $self->SUPER::check;
}
=back
=head1 VERSION
-$Id: cust_pay_batch.pm,v 1.6 2002-02-22 23:08:11 ivan Exp $
+$Id: cust_pay_batch.pm,v 1.7 2003-08-05 00:20:42 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
index a423c5518..f59b45ab1 100644
--- a/FS/FS/cust_pkg.pm
+++ b/FS/FS/cust_pkg.pm
@@ -249,7 +249,7 @@ sub check {
$self->manual_flag($1);
}
- ''; #no error
+ $self->SUPER::check;
}
=item cancel
diff --git a/FS/FS/cust_refund.pm b/FS/FS/cust_refund.pm
index 763671736..250bd20e0 100644
--- a/FS/FS/cust_refund.pm
+++ b/FS/FS/cust_refund.pm
@@ -260,14 +260,14 @@ sub check {
$self->otaker(getotaker);
- ''; #no error
+ $self->SUPER::check;
}
=back
=head1 VERSION
-$Id: cust_refund.pm,v 1.20 2002-11-19 09:51:58 ivan Exp $
+$Id: cust_refund.pm,v 1.21 2003-08-05 00:20:42 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm
index c0cb6f4e9..7aa311ba5 100644
--- a/FS/FS/cust_svc.pm
+++ b/FS/FS/cust_svc.pm
@@ -234,7 +234,7 @@ sub check {
if scalar(@cust_svc) >= $quantity && (!$ignore_quantity || !$quantity);
}
- ''; #no error
+ $self->SUPER::check;
}
=item part_svc
diff --git a/FS/FS/cust_tax_exempt.pm b/FS/FS/cust_tax_exempt.pm
index ab873c0a7..da0de000a 100644
--- a/FS/FS/cust_tax_exempt.pm
+++ b/FS/FS/cust_tax_exempt.pm
@@ -111,6 +111,7 @@ sub check {
|| $self->ut_number('year') #check better
|| $self->ut_number('month') #check better
|| $self->ut_money('amount')
+ || $self->SUPER::check
;
}
diff --git a/FS/FS/domain_record.pm b/FS/FS/domain_record.pm
index 77b955088..ea0c48d4f 100644
--- a/FS/FS/domain_record.pm
+++ b/FS/FS/domain_record.pm
@@ -271,7 +271,7 @@ sub check {
die "ack!";
}
- ''; #no error
+ $self->SUPER::check;
}
=item increment_serial
@@ -332,7 +332,7 @@ sub zone {
=head1 VERSION
-$Id: domain_record.pm,v 1.15 2003-04-29 18:28:50 khoff Exp $
+$Id: domain_record.pm,v 1.16 2003-08-05 00:20:43 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/export_svc.pm b/FS/FS/export_svc.pm
index da9ac698a..c104e4538 100644
--- a/FS/FS/export_svc.pm
+++ b/FS/FS/export_svc.pm
@@ -105,6 +105,7 @@ sub check {
|| $self->ut_foreign_key('exportnum', 'part_export', 'exportnum')
|| $self->ut_number('svcpart')
|| $self->ut_foreign_key('svcpart', 'part_svc', 'svcpart')
+ || $self->SUPER::check
;
}
diff --git a/FS/FS/msgcat.pm b/FS/FS/msgcat.pm
index fa10d34fa..855b8b291 100644
--- a/FS/FS/msgcat.pm
+++ b/FS/FS/msgcat.pm
@@ -113,7 +113,7 @@ sub check {
$self->locale =~ /^([\w\@]+)$/ or return "illegal locale: ". $self->locale;
$self->locale($1);
- ''; #no error
+ $self->SUPER::check
}
=back
diff --git a/FS/FS/nas.pm b/FS/FS/nas.pm
index 58c6827ea..2d17df899 100644
--- a/FS/FS/nas.pm
+++ b/FS/FS/nas.pm
@@ -114,7 +114,9 @@ sub check {
|| $self->ut_text('nas')
|| $self->ut_ip('nasip')
|| $self->ut_domain('nasfqdn')
- || $self->ut_numbern('last');
+ || $self->ut_numbern('last')
+ || $self->SUPER::check
+ ;
}
=item heartbeat TIMESTAMP
@@ -136,7 +138,7 @@ sub heartbeat {
=head1 VERSION
-$Id: nas.pm,v 1.6 2002-03-04 12:48:49 ivan Exp $
+$Id: nas.pm,v 1.7 2003-08-05 00:20:43 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/part_bill_event.pm b/FS/FS/part_bill_event.pm
index e0e4f3f19..9e5d82161 100644
--- a/FS/FS/part_bill_event.pm
+++ b/FS/FS/part_bill_event.pm
@@ -162,8 +162,7 @@ sub check {
}
}
- '';
-
+ $self->SUPER::check;
}
=back
diff --git a/FS/FS/part_export.pm b/FS/FS/part_export.pm
index ff519969d..ab0a4b566 100644
--- a/FS/FS/part_export.pm
+++ b/FS/FS/part_export.pm
@@ -285,7 +285,7 @@ sub check {
#check exporttype?
- ''; #no error
+ $self->SUPER::check;
}
#=item part_svc
@@ -667,6 +667,19 @@ END
},
;
+tie my %router_options, 'Tie::IxHash',
+ 'protocol' => {
+ label=>'Protocol',
+ type =>'select',
+ options => [qw(telnet ssh)],
+ default => 'telnet'},
+ 'insert' => {label=>'Insert command', default=>'' },
+ 'delete' => {label=>'Delete command', default=>'' },
+ 'replace' => {label=>'Replace command', default=>'' },
+ 'Timeout' => {label=>'Time to wait for prompt', default=>'20' },
+ 'Prompt' => {label=>'Prompt string', default=>'#' }
+;
+
tie my %domain_shellcommands_options, 'Tie::IxHash',
'user' => { lable=>'Remote username', default=>'root' },
'useradd' => { label=>'Insert command',
@@ -1013,8 +1026,12 @@ tie my %forward_shellcommands_options, 'Tie::IxHash',
},
'svc_broadband' => {
+ 'router' => {
+ 'desc' => 'Send a command to a router.',
+ 'options' => \%router_options,
+ 'notes' => '',
+ },
},
-
);
=back
diff --git a/FS/FS/part_export_option.pm b/FS/FS/part_export_option.pm
index a0b19fde1..33b5e5a67 100644
--- a/FS/FS/part_export_option.pm
+++ b/FS/FS/part_export_option.pm
@@ -115,7 +115,7 @@ sub check {
#check options & values?
- ''; #no error
+ $self->SUPER::check;
}
=back
diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm
index 6525864c4..12ee804ff 100644
--- a/FS/FS/part_pkg.pm
+++ b/FS/FS/part_pkg.pm
@@ -241,6 +241,7 @@ sub check {
|| $self->ut_enum('recurtax', [ '', 'Y' ] )
|| $self->ut_textn('taxclass')
|| $self->ut_enum('disabled', [ '', 'Y' ] )
+ || $self->SUPER::check
;
}
diff --git a/FS/FS/part_pop_local.pm b/FS/FS/part_pop_local.pm
index 0b7cdf6c9..f7d5eac9a 100644
--- a/FS/FS/part_pop_local.pm
+++ b/FS/FS/part_pop_local.pm
@@ -92,6 +92,7 @@ sub check {
or $self->ut_text('state')
or $self->ut_number('npa')
or $self->ut_number('nxx')
+ or $self->SUPER::check
;
}
@@ -100,7 +101,7 @@ sub check {
=head1 VERSION
-$Id: part_pop_local.pm,v 1.1 2001-09-26 09:17:06 ivan Exp $
+$Id: part_pop_local.pm,v 1.2 2003-08-05 00:20:44 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/part_referral.pm b/FS/FS/part_referral.pm
index 23885dffd..f30ddad01 100644
--- a/FS/FS/part_referral.pm
+++ b/FS/FS/part_referral.pm
@@ -93,6 +93,7 @@ sub check {
$self->ut_numbern('refnum')
|| $self->ut_text('referral')
+ || $self->SUPER::check
;
}
diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm
index 63bc2ad1c..aacc3ab48 100644
--- a/FS/FS/part_svc.pm
+++ b/FS/FS/part_svc.pm
@@ -68,7 +68,7 @@ TODOC:
=item I<svcdb>__I<field> - Default or fixed value for I<field> in I<svcdb>.
-=item I<svcdb>__I<field>_flag - defines I<svcdb>__I<field> action: null, `D' for default, or `F' for fixed
+=item I<svcdb>__I<field>_flag - defines I<svcdb>__I<field> action: null, `D' for default, or `F' for fixed. For virtual fields, can also be 'X' for excluded.
TODOC: EXTRA_FIELDS_ARRAYREF
@@ -113,7 +113,7 @@ sub insert {
} );
my $flag = $self->getfield($svcdb.'__'.$field.'_flag');
- if ( uc($flag) =~ /^([DF])$/ ) {
+ if ( uc($flag) =~ /^([DFX])$/ ) {
$part_svc_column->setfield('columnflag', $1);
$part_svc_column->setfield('columnvalue',
$self->getfield($svcdb.'__'.$field)
@@ -201,7 +201,7 @@ sub replace {
} );
my $flag = $new->getfield($svcdb.'__'.$field.'_flag');
- if ( uc($flag) =~ /^([DF])$/ ) {
+ if ( uc($flag) =~ /^([DFX])$/ ) {
$part_svc_column->setfield('columnflag', $1);
$part_svc_column->setfield('columnvalue',
$new->getfield($svcdb.'__'.$field)
@@ -254,7 +254,7 @@ sub check {
my @fields = eval { fields( $recref->{svcdb} ) }; #might die
return "Unknown svcdb!" unless @fields;
- ''; #no error
+ $self->SUPER::check;
}
=item part_svc_column COLUMNNAME
diff --git a/FS/FS/part_svc_column.pm b/FS/FS/part_svc_column.pm
index 37e841e87..885155be3 100644
--- a/FS/FS/part_svc_column.pm
+++ b/FS/FS/part_svc_column.pm
@@ -41,7 +41,7 @@ fields are currently supported:
=item columnvalue - default or fixed value for the column
-=item columnflag - null, D or F
+=item columnflag - null, D, F, X (virtual fields)
=back
@@ -91,18 +91,18 @@ sub check {
;
return $error if $error;
- $self->columnflag =~ /^([DF])$/
+ $self->columnflag =~ /^([DFX])$/
or return "illegal columnflag ". $self->columnflag;
$self->columnflag(uc($1));
- ''; #no error
+ $self->SUPER::check;
}
=back
=head1 VERSION
-$Id: part_svc_column.pm,v 1.1 2001-09-07 20:49:15 ivan Exp $
+$Id: part_svc_column.pm,v 1.2 2003-08-05 00:20:44 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/part_virtual_field.pm b/FS/FS/part_virtual_field.pm
new file mode 100755
index 000000000..03c34cca5
--- /dev/null
+++ b/FS/FS/part_virtual_field.pm
@@ -0,0 +1,303 @@
+package FS::part_virtual_field;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs qsearch dbdef );
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::part_virtual_field - Object methods for part_virtual_field records
+
+=head1 SYNOPSIS
+
+ use FS::part_virtual_field;
+
+ $record = new FS::part_virtual_field \%hash;
+ $record = new FS::part_virtual_field { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_virtual_field object represents the definition of a virtual field
+(see the BACKGROUND section). FS::part_virtual_field contains the name and
+base table of the field, as well as validation rules and UI hints about the
+display of the field. The actual data is stored in FS::virtual_field; see
+its manpage for details.
+
+FS::part_virtual_field inherits from FS::Record. The following fields are
+currently supported:
+
+=over 2
+
+=item vfieldpart - primary key (assigned automatically)
+
+=item name - name of the field
+
+=item dbtable - table for which this virtual field is defined
+
+=item check_block - Perl code to validate/normalize data
+
+=item list_source - Perl code to generate a list of values (UI hint)
+
+=item length - expected length of the value (UI hint)
+
+=item label - descriptive label for the field (UI hint)
+
+=item sequence - sort key (UI hint; unimplemented)
+
+=back
+
+=head1 BACKGROUND
+
+"Form is none other than emptiness,
+ and emptiness is none other than form."
+-- Heart Sutra
+
+The virtual field mechanism allows site admins to make trivial changes to
+the Freeside database schema without modifying the code. Specifically, the
+user can add custom-defined 'fields' to the set of data tracked by Freeside
+about objects such as customers and services. These fields are not associated
+with any logic in the core Freeside system, but may be referenced in peripheral
+code such as exports, price calculations, or alternate interfaces, or may just
+be stored in the database for future reference.
+
+This system was originally devised for svc_broadband, which (by necessity)
+comprises such a wide range of access technologies that no static set of fields
+could contain all the information needed by the exports. In an appalling
+display of False Laziness, a parallel mechanism was implemented for the
+router table, to store properties such as passwords to configure routers.
+
+The original system treated svc_broadband custom fields (sb_fields) as records
+in a completely separate table. Any code that accessed or manipulated these
+fields had to be aware that they were I<not> fields in svc_broadband, but
+records in sb_field. For example, code that inserted a svc_broadband with
+several custom fields had to create an FS::svc_broadband object, call its
+insert() method, and then create several FS::sb_field objects and call I<their>
+insert() methods.
+
+This created a problem for exports. The insert method on any FS::svc_Common
+object (including svc_broadband) automatically triggers exports after the
+record has been inserted. However, at this point, the sb_fields had not yet
+been inserted, so the export could not rely on their presence, which was the
+original purpose of sb_fields.
+
+Hence the new system. Virtual fields are appended to the field list of every
+record at the FS::Record level, whether the object is created ex nihilo with
+new() or fetched with qsearch(). The fields() method now returns a list of
+both real and virtual fields. The insert(), replace(), and delete() methods
+now update both the base table and the virtual fields, in a single transaction.
+
+A new method is provided, virtual_fields(), which gives only the virtual
+fields. UI code that dynamically generates form widgets to edit virtual field
+data should use this to figure out what fields are defined. (See below.)
+
+Subclasses may override virtual_fields() to restrict the set of virtual
+fields available. Some discipline and sanity on the part of the programmer
+are required; in particular, this function should probably not depend on any
+fields in the record other than the primary key, since the others may change
+after the object is instantiated. (Making it depend on I<virtual> fields is
+just asking for pain.) One use of this is seen in FS::svc_Common; another
+possibility is field-level access control based on FS::UID::getotaker().
+
+As a trivial case, a subclass may opt out of supporting virtual fields with
+the following code:
+
+sub virtual_fields { () }
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Create a new record. To add the record to the database, see "insert".
+
+=cut
+
+sub table { 'part_virtual_field'; }
+sub virtual_fields { () }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this record from the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+If there is an error, returns the error, otherwise returns false.
+Called by the insert and replace methods.
+
+=back
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error = $self->ut_text('name') ||
+ $self->ut_text('dbtable') ||
+ $self->ut_number('length')
+ ;
+ return $error if $error;
+
+ # Make sure it's a real table with a numeric primary key
+ my ($table, $pkey);
+ if($table = $FS::Record::dbdef->table($self->dbtable)) {
+ if($pkey = $table->primary_key) {
+ if($table->column($pkey)->type =~ /int/i) {
+ # this is what it should be
+ } else {
+ $error = "$table.$pkey is not an integer";
+ }
+ } else {
+ $error = "$table does not have a single-field primary key";
+ }
+ } else {
+ $error = "$table does not exist in the schema";
+ }
+ return $error if $error;
+
+ # Possibly some sanity checks for check_block and list_source?
+
+ $self->SUPER::check;
+}
+
+=item list
+
+Evaluates list_source.
+
+=cut
+
+sub list {
+ my $self = shift;
+ return () unless $self->list_source;
+
+ my @opts = eval($self->list_source);
+ if($@) {
+ warn $@;
+ return ();
+ } else {
+ return @opts;
+ }
+}
+
+=item widget UI_TYPE MODE [ VALUE ]
+
+Generates UI code for a widget suitable for editing/viewing the field, based on
+list_source and length.
+
+The only UI_TYPE currently supported is 'HTML', and the only MODE is 'view'.
+Others will be added later.
+
+In HTML, all widgets are assumed to be table rows. View widgets look like
+<TR><TD ALIGN="right">Label</TD><TD BGCOLOR="#ffffff">Value</TD></TR>
+
+(Most of the display style stuff, such as the colors, should probably go into
+a separate module specific to the UI. That can wait, though. The API for
+this function won't change.)
+
+VALUE (optional) is the current value of the field.
+
+=cut
+
+sub widget {
+ my $self = shift;
+ my ($ui_type, $mode, $value) = @_;
+ my $text;
+ my $label = $self->label || $self->name;
+
+ if ($ui_type eq 'HTML') {
+ if ($mode eq 'view') {
+ $text = q!<TR><TD ALIGN="right">! . $label .
+ q!</TD><TD BGCOLOR="#ffffff">! . $value .
+ q!</TD></TR>! . "\n";
+ } elsif ($mode eq 'edit') {
+ $text = q!<TR><TD ALIGN="right">! . $label .
+ q!</TD><TD>!;
+ if ($self->list_source) {
+ $text .= q!<SELECT NAME="! . $self->name .
+ q!" SIZE=1>! . "\n";
+ foreach ($self->list) {
+ $text .= q!<OPTION VALUE="! . $_ . q!"!;
+ $text .= ' SELECTED' if ($_ eq $value);
+ $text .= '>' . $_ . '</OPTION>' . "\n";
+ }
+ } else {
+ $text .= q!<INPUT NAME="! . $self->name .
+ q!" VALUE="! . $value . q!"!;
+ if ($self->length) {
+ $text .= q! SIZE="! . $self->length . q!"!;
+ }
+ $text .= '>';
+ }
+ $text .= q!</TD></TR>! . "\n";
+ } else {
+ return '';
+ }
+ } else {
+ return '';
+ }
+ return $text;
+}
+
+=head1 VERSION
+
+$Id: part_virtual_field.pm,v 1.2 2003-08-05 00:20:45 khoff Exp $
+
+=head1 NOTES
+
+=head2 Semantics of check_block:
+
+This has been changed from the sb_field implementation to make check_blocks
+simpler and more natural to Perl programmers who work on things other than
+Freeside.
+
+The check_block is eval'd with the (proposed) new value of the field in $_,
+and the object to be updated in $self. Its return value is ignored. The
+check_block may change the value of $_ to override the proposed value, or
+call die() (with an appropriate error message) to reject the update entirely;
+the error string will be returned as the output of the check() method.
+
+This makes check_blocks like
+
+C<s/foo/bar/>
+
+do what you expect.
+
+The check_block is expected NOT to do anything freaky to $self, like modifying
+other fields or calling $self->check(). You have been warned.
+
+(FIXME: Rewrite some of the warnings from part_sb_field and insert here.)
+
+=head1 BUGS
+
+None. It's absolutely falwless.
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::virtual_field>
+
+=cut
+
+1;
+
+
diff --git a/FS/FS/pkg_svc.pm b/FS/FS/pkg_svc.pm
index 3c544ffd8..2ac1a558c 100644
--- a/FS/FS/pkg_svc.pm
+++ b/FS/FS/pkg_svc.pm
@@ -108,7 +108,7 @@ sub check {
return "Unknown pkgpart!" unless $self->part_pkg;
return "Unknown svcpart!" unless $self->part_svc;
- ''; #no error
+ $self->SUPER::check;
}
=item part_pkg
@@ -137,7 +137,7 @@ sub part_svc {
=head1 VERSION
-$Id: pkg_svc.pm,v 1.3 2002-06-10 01:39:50 khoff Exp $
+$Id: pkg_svc.pm,v 1.4 2003-08-05 00:20:45 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/port.pm b/FS/FS/port.pm
index 13455ca89..620030afc 100644
--- a/FS/FS/port.pm
+++ b/FS/FS/port.pm
@@ -113,7 +113,7 @@ sub check {
unless $self->ip || $self->nasport;
return "Unknown nasnum"
unless qsearchs('nas', { 'nasnum' => $self->nasnum } );
- ''; #no error
+ $self->SUPER::check;
}
=item session
@@ -133,7 +133,7 @@ sub session {
=head1 VERSION
-$Id: port.pm,v 1.5 2001-02-14 04:33:06 ivan Exp $
+$Id: port.pm,v 1.6 2003-08-05 00:20:45 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/prepay_credit.pm b/FS/FS/prepay_credit.pm
index 7ed9b8344..a9d26d151 100644
--- a/FS/FS/prepay_credit.pm
+++ b/FS/FS/prepay_credit.pm
@@ -108,6 +108,7 @@ sub check {
|| $self->ut_alpha('identifier')
|| $self->ut_money('amount')
|| $self->utnumbern('seconds')
+ || $self->SUPER::check
;
}
diff --git a/FS/FS/queue.pm b/FS/FS/queue.pm
index d35dc883f..634f7f4bd 100644
--- a/FS/FS/queue.pm
+++ b/FS/FS/queue.pm
@@ -207,7 +207,7 @@ sub check {
$self->status('new') unless $self->status;
$self->_date(time) unless $self->_date;
- ''; #no error
+ $self->SUPER::check;
}
=item args
@@ -385,7 +385,7 @@ END
=head1 VERSION
-$Id: queue.pm,v 1.15 2002-07-02 06:48:59 ivan Exp $
+$Id: queue.pm,v 1.16 2003-08-05 00:20:46 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/queue_arg.pm b/FS/FS/queue_arg.pm
index 08fe47341..d23ee2afd 100644
--- a/FS/FS/queue_arg.pm
+++ b/FS/FS/queue_arg.pm
@@ -100,14 +100,14 @@ sub check {
;
return $error if $error;
- ''; #no error
+ $self->SUPER::check;
}
=back
=head1 VERSION
-$Id: queue_arg.pm,v 1.1 2001-09-11 00:08:18 ivan Exp $
+$Id: queue_arg.pm,v 1.2 2003-08-05 00:20:46 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/queue_depend.pm b/FS/FS/queue_depend.pm
index 4a4e3c55c..bc910d8e9 100644
--- a/FS/FS/queue_depend.pm
+++ b/FS/FS/queue_depend.pm
@@ -103,6 +103,7 @@ sub check {
$self->ut_numbern('dependnum')
|| $self->ut_foreign_key('jobnum', 'queue', 'jobnum')
|| $self->ut_foreign_key('depend_jobnum', 'queue', 'jobnum')
+ || $self->SUPER::check
;
}
diff --git a/FS/FS/radius_usergroup.pm b/FS/FS/radius_usergroup.pm
index 647621d28..9bba057c9 100644
--- a/FS/FS/radius_usergroup.pm
+++ b/FS/FS/radius_usergroup.pm
@@ -100,6 +100,7 @@ sub check {
|| $self->ut_number('svcnum')
|| $self->ut_foreign_key('svcnum','svc_acct','svcnum')
|| $self->ut_text('groupname')
+ || $self->SUPER::check
;
}
diff --git a/FS/FS/router.pm b/FS/FS/router.pm
index 3f9459a01..2554ce86b 100755
--- a/FS/FS/router.pm
+++ b/FS/FS/router.pm
@@ -85,7 +85,7 @@ sub check {
|| $self->ut_text('routername');
return $error if $error;
- '';
+ $self->SUPER::check;
}
=item addr_block
@@ -100,18 +100,6 @@ sub addr_block {
return qsearch('addr_block', { routernum => $self->routernum });
}
-=item router_field
-
-Returns a list of FS::router_field objects assigned to this object.
-
-=cut
-
-sub router_field {
- my $self = shift;
-
- return qsearch('router_field', { routernum => $self->routernum });
-}
-
=item part_svc_router
Returns a list of FS::part_svc_router objects associated with this
@@ -147,7 +135,7 @@ $Id:
=head1 SEE ALSO
-FS::svc_broadband, FS::router, FS::addr_block, FS::router_field, FS::part_svc,
+FS::svc_broadband, FS::router, FS::addr_block, FS::part_svc,
schema.html from the base documentation.
=cut
diff --git a/FS/FS/session.pm b/FS/FS/session.pm
index de0f2a76a..2ad594cf2 100644
--- a/FS/FS/session.pm
+++ b/FS/FS/session.pm
@@ -216,7 +216,7 @@ sub check {
return $error if $error;
return "Unknown svcnum"
unless qsearchs('svc_acct', { 'svcnum' => $self->svcnum } );
- '';
+ $self->SUPER::check;
}
=item nas_heartbeat
@@ -247,7 +247,7 @@ sub svc_acct {
=head1 VERSION
-$Id: session.pm,v 1.7 2001-04-15 13:35:12 ivan Exp $
+$Id: session.pm,v 1.8 2003-08-05 00:20:46 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm
index 87b6097aa..524e55086 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 );
-use FS::Record qw( qsearchs fields dbh );
+use FS::Record qw( qsearch qsearchs fields dbh );
use FS::cust_svc;
use FS::part_svc;
use FS::queue;
@@ -28,6 +28,58 @@ inherit from, i.e. FS::svc_acct. FS::svc_Common inherits from FS::Record.
=over 4
+=cut
+
+sub virtual_fields {
+
+ # This restricts the fields based on part_svc_column and the svcpart of
+ # the service. There are four possible cases:
+ # 1. svcpart passed as part of the svc_x hash.
+ # 2. svcpart fetched via cust_svc based on svcnum.
+ # 3. No svcnum or svcpart. In this case, return ALL the fields with
+ # dbtable eq $self->table.
+ # 4. Called via "fields('svc_acct')" or something similar. In this case
+ # there is no $self object.
+
+ my $self = shift;
+ my $svcpart;
+ my @vfields = $self->SUPER::virtual_fields;
+
+ return @vfields unless (ref $self); # Case 4
+
+ if ($self->svcpart) { # Case 1
+ $svcpart = $self->svcpart;
+ } elsif (my $cust_svc = $self->cust_svc) { # Case 2
+ $svcpart = $cust_svc->svcpart;
+ } else { # Case 3
+ $svcpart = '';
+ }
+
+ if ($svcpart) { #Cases 1 and 2
+ my %flags = map { $_->columnname, $_->columnflag } (
+ qsearch ('part_svc_column', { svcpart => $svcpart } )
+ );
+ return grep { not ($flags{$_} eq 'X') } @vfields;
+ } else { # Case 3
+ return @vfields;
+ }
+ return ();
+}
+
+=item check
+
+Checks the validity of fields in this record.
+
+At present, this does nothing but call FS::Record::check (which, in turn,
+does nothing but run virtual field checks).
+
+=cut
+
+sub check {
+ my $self = shift;
+ $self->SUPER::check;
+}
+
=item insert [ JOBNUM_ARRAYREF ]
Adds this record to the database. If there is an error, returns the error,
@@ -254,7 +306,7 @@ sub setx {
#set default/fixed/whatever fields from part_svc
my $table = $self->table;
- foreach my $field ( grep { $_ ne 'svcnum' } fields($table) ) {
+ foreach my $field ( grep { $_ ne 'svcnum' } $self->fields ) {
my $part_svc_column = $part_svc->part_svc_column($field);
if ( $part_svc_column->columnflag eq $x ) {
$self->setfield( $field, $part_svc_column->columnvalue );
@@ -364,7 +416,7 @@ sub cancel { ''; }
=head1 VERSION
-$Id: svc_Common.pm,v 1.12 2002-06-14 11:22:53 ivan Exp $
+$Id: svc_Common.pm,v 1.13 2003-08-05 00:20:47 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm
index 8a6f2c403..0ee7a7286 100644
--- a/FS/FS/svc_acct.pm
+++ b/FS/FS/svc_acct.pm
@@ -832,7 +832,7 @@ sub check {
": ". $recref->{_password};
}
- ''; #no error
+ $self->SUPER::check;
}
=item radius
diff --git a/FS/FS/svc_acct_pop.pm b/FS/FS/svc_acct_pop.pm
index 196ab7ebb..f98f91a4f 100644
--- a/FS/FS/svc_acct_pop.pm
+++ b/FS/FS/svc_acct_pop.pm
@@ -93,6 +93,7 @@ sub check {
or $self->ut_number('ac')
or $self->ut_number('exch')
or $self->ut_numbern('loc')
+ or $self->SUPER::check
;
}
@@ -187,7 +188,7 @@ END
=head1 VERSION
-$Id: svc_acct_pop.pm,v 1.9 2003-07-04 01:37:46 ivan Exp $
+$Id: svc_acct_pop.pm,v 1.10 2003-08-05 00:20:47 khoff Exp $
=head1 BUGS
diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm
index 45f6c3601..ec915327b 100755
--- a/FS/FS/svc_broadband.pm
+++ b/FS/FS/svc_broadband.pm
@@ -116,50 +116,6 @@ returns the error, otherwise returns false.
# Standard FS::svc_Common::replace
-=item sb_field
-
-Returns a list of FS::sb_field objects assigned to this object.
-
-=cut
-
-sub sb_field {
- my $self = shift;
-
- return qsearch( 'sb_field', { svcnum => $self->svcnum } );
-}
-
-=item sb_field_hashref
-
-Returns a hashref of the FS::sb_field key/value pairs for this object.
-
-Deprecated. Please don't use it.
-
-=cut
-
-# Kristian wrote this, but don't hold it against him. He was under a powerful
-# distracting influence whom he evidently found much more interesting than
-# svc_broadband.pm. I can't say I blame him.
-
-sub sb_field_hashref {
- my $self = shift;
- my $svcpart = shift;
-
- if ((not $svcpart) && ($self->cust_svc)) {
- $svcpart = $self->cust_svc->svcpart;
- }
-
- my $hashref = {};
-
- map {
- my $sb_field = qsearchs('sb_field', { sbfieldpart => $_->sbfieldpart,
- svcnum => $self->svcnum });
- $hashref->{$_->getfield('name')} = $sb_field ? $sb_field->getfield('value') : '';
- } qsearch('part_sb_field', { svcpart => $svcpart });
-
- return $hashref;
-
-}
-
=item suspend
Called by the suspend method of FS::cust_pkg (see FS::cust_pkg).
@@ -223,8 +179,7 @@ sub check {
return 'Router '.$router->routernum.' cannot provide svcpart '.$self->svcpart;
}
-
- ''; #no error
+ $self->SUPER::check;
}
=item NetAddr
@@ -267,19 +222,11 @@ sub allowed_routers {
=head1 BUGS
-I think there's one place in the code where we actually use sb_field_hashref.
-That's a bug in itself.
-
-The real problem with it is that we're still grappling with the question of how
-tightly xfields should be integrated with real fields. There are a few
-different directions we could go with it--we I<could> override several
-functions in Record so that xfields behave almost exactly like real fields (can
-be set with setfield(), appear in fields() and hash(), used as criteria in
-qsearch(), etc.).
+The business with sb_field has been 'fixed', in a manner of speaking.
=head1 SEE ALSO
-FS::svc_Common, FS::Record, FS::addr_block, FS::sb_field,
+FS::svc_Common, FS::Record, FS::addr_block,
FS::part_svc, schema.html from the base documentation.
=cut
diff --git a/FS/FS/svc_domain.pm b/FS/FS/svc_domain.pm
index 32b94563d..ff0fa2f61 100644
--- a/FS/FS/svc_domain.pm
+++ b/FS/FS/svc_domain.pm
@@ -342,7 +342,9 @@ sub check {
return "Unknown catchall" unless $svc_acct;
}
- $self->ut_textn('purpose');
+ my $error = $self->ut_textn('purpose')
+ or $self->SUPER::check;
+ return $error if $error;
}
diff --git a/FS/FS/svc_forward.pm b/FS/FS/svc_forward.pm
index 2b1fb9225..7a121b835 100644
--- a/FS/FS/svc_forward.pm
+++ b/FS/FS/svc_forward.pm
@@ -241,7 +241,7 @@ sub check {
$self->dst('');
}
- ''; #no error
+ $self->SUPER::check;
}
=item srcsvc_acct
diff --git a/FS/FS/svc_www.pm b/FS/FS/svc_www.pm
index d7a42c8ae..7e8908346 100644
--- a/FS/FS/svc_www.pm
+++ b/FS/FS/svc_www.pm
@@ -234,7 +234,8 @@ sub check {
return "Unknown usersvc (svc_acct.svcnum): ". $self->usersvc
unless qsearchs('svc_acct', { 'svcnum' => $self->usersvc } );
- ''; #no error
+ $self->SUPER::check;
+
}
=item domain_record
diff --git a/FS/FS/type_pkgs.pm b/FS/FS/type_pkgs.pm
index efba60dbd..5b3b11c09 100644
--- a/FS/FS/type_pkgs.pm
+++ b/FS/FS/type_pkgs.pm
@@ -91,7 +91,7 @@ sub check {
return "Unknown pkgpart"
unless qsearchs( 'part_pkg', { 'pkgpart' => $self->pkgpart } );
- ''; #no error
+ $self->SUPER::check;
}
=item part_pkg
@@ -111,7 +111,7 @@ sub part_pkg {
=head1 VERSION
-$Id: type_pkgs.pm,v 1.2 2002-10-04 12:57:06 ivan Exp $
+$Id: type_pkgs.pm,v 1.3 2003-08-05 00:20:48 khoff Exp $
=head1 BUGS
diff --git a/FS/MANIFEST b/FS/MANIFEST
index 846f37310..0fbbf5cb5 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -92,9 +92,8 @@ FS/part_pop_local.pm
FS/part_referral.pm
FS/part_svc.pm
FS/part_svc_column.pm
-FS/part_router_field.pm
-FS/part_sb_field.pm
FS/part_svc_router.pm
+FS/part_virtual_field.pm
FS/pkg_svc.pm
FS/svc_Common.pm
FS/svc_acct.pm
@@ -102,7 +101,6 @@ FS/svc_acct_pop.pm
FS/svc_broadband.pm
FS/svc_domain.pm
FS/router.pm
-FS/router_field.pm
FS/type_pkgs.pm
FS/nas.pm
FS/port.pm
@@ -111,7 +109,6 @@ FS/domain_record.pm
FS/prepay_credit.pm
FS/svc_www.pm
FS/svc_forward.pm
-FS/sb_field.pm
FS/raddb.pm
FS/radius_usergroup.pm
FS/queue.pm
diff --git a/FS/bin/freeside-setup b/FS/bin/freeside-setup
index 734744efe..2cb555e18 100755
--- a/FS/bin/freeside-setup
+++ b/FS/bin/freeside-setup
@@ -1031,7 +1031,7 @@ sub tables_hash_hack {
'columns' => [
'routernum', 'serial', '', '',
'routername', 'varchar', '', $char_d,
- 'svcnum', 'int', '0', '',
+ 'svcnum', 'int', 'NULL', '',
],
'primary_key' => 'routernum',
'unique' => [],
@@ -1048,30 +1048,6 @@ sub tables_hash_hack {
'index' => [],
},
- 'part_router_field' => {
- 'columns' => [
- 'routerfieldpart', 'serial', '', '',
- 'name', 'varchar', '', $char_d,
- 'length', 'int', '', '',
- 'check_block', 'text', 'NULL', '',
- 'list_source', 'text', 'NULL', '',
- ],
- 'primary_key' => 'routerfieldpart',
- 'unique' => [],
- 'index' => [],
- },
-
- 'router_field' => {
- 'columns' => [
- 'routerfieldpart', 'int', '', '',
- 'routernum', 'int', '', '',
- 'value', 'varchar', '', 128,
- ],
- 'primary_key' => '',
- 'unique' => [ [ 'routerfieldpart', 'routernum' ] ],
- 'index' => [],
- },
-
'addr_block' => {
'columns' => [
'blocknum', 'serial', '', '',
@@ -1084,31 +1060,6 @@ sub tables_hash_hack {
'index' => [],
},
- 'part_sb_field' => {
- 'columns' => [
- 'sbfieldpart', 'serial', '', '',
- 'svcpart', 'int', '', '',
- 'name', 'varchar', '', $char_d,
- 'length', 'int', '', '',
- 'check_block', 'text', 'NULL', '',
- 'list_source', 'text', 'NULL', '',
- ],
- 'primary_key' => 'sbfieldpart',
- 'unique' => [ [ 'sbfieldpart', 'svcpart' ] ],
- 'index' => [],
- },
-
- 'sb_field' => {
- 'columns' => [
- 'sbfieldpart', 'int', '', '',
- 'svcnum', 'int', '', '',
- 'value', 'varchar', '', 128,
- ],
- 'primary_key' => '',
- 'unique' => [ [ 'sbfieldpart', 'svcnum' ] ],
- 'index' => [],
- },
-
'svc_broadband' => {
'columns' => [
'svcnum', 'int', '', '',
@@ -1122,6 +1073,32 @@ sub tables_hash_hack {
'index' => [],
},
+ 'part_virtual_field' => {
+ 'columns' => [
+ 'vfieldpart', 'int', '', '',
+ 'dbtable', 'varchar', '', 32,
+ 'name', 'varchar', '', 32,
+ 'check_block', 'text', 'NULL', '',
+ 'length', 'int', 'NULL', '',
+ 'list_source', 'text', 'NULL', '',
+ 'label', 'varchar', 'NULL', 80,
+ ],
+ 'primary_key' => 'vfieldpart',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'virtual_field' => {
+ 'columns' => [
+ 'recnum', 'int', '', '',
+ 'vfieldpart', 'int', '', '',
+ 'value', 'varchar', '', 128,
+ ],
+ 'primary_key' => '',
+ 'unique' => [ [ 'vfieldpart', 'recnum' ] ],
+ 'index' => [],
+ },
+
);
%tables;
diff --git a/htetc/global.asa b/htetc/global.asa
index d87f1eac6..6f76fd256 100644
--- a/htetc/global.asa
+++ b/htetc/global.asa
@@ -42,6 +42,7 @@ use FS::part_pkg;
use FS::part_referral;
use FS::part_svc;
use FS::part_svc_router;
+use FS::part_virtual_field;
use FS::pkg_svc;
use FS::port;
use FS::queue qw(joblisting);
@@ -53,11 +54,7 @@ use FS::svc_domain;
use FS::svc_forward;
use FS::svc_www;
use FS::router;
-use FS::part_router_field;
-use FS::router_field;
use FS::addr_block;
-use FS::part_sb_field;
-use FS::sb_field;
use FS::svc_broadband;
use FS::type_pkgs;
use FS::part_export;
diff --git a/htetc/handler.pl b/htetc/handler.pl
index 481d5a2d9..c81db8664 100644
--- a/htetc/handler.pl
+++ b/htetc/handler.pl
@@ -100,6 +100,7 @@ sub handler
use FS::part_referral;
use FS::part_svc;
use FS::part_svc_router;
+ use FS::part_virtual_field;
use FS::pkg_svc;
use FS::port;
use FS::queue qw(joblisting);
@@ -112,11 +113,7 @@ sub handler
use FS::svc_forward;
use FS::svc_www;
use FS::router;
- use FS::part_router_field;
- use FS::router_field;
use FS::addr_block;
- use FS::part_sb_field;
- use FS::sb_field;
use FS::svc_broadband;
use FS::type_pkgs;
use FS::part_export;
diff --git a/htetc/handler.pl-1.0x b/htetc/handler.pl-1.0x
index 8840b0816..def579f49 100644
--- a/htetc/handler.pl-1.0x
+++ b/htetc/handler.pl-1.0x
@@ -95,6 +95,7 @@ sub handler
use FS::part_referral;
use FS::part_svc;
use FS::part_svc_router;
+ use FS::part_virtual_field;
use FS::pkg_svc;
use FS::port;
use FS::queue qw(joblisting);
@@ -107,11 +108,7 @@ sub handler
use FS::svc_forward;
use FS::svc_www;
use FS::router;
- use FS::part_router_field;
- use FS::router_field;
use FS::addr_block;
- use FS::part_sb_field;
- use FS::sb_field;
use FS::svc_broadband;
use FS::type_pkgs;
use FS::part_export;
diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi
index 7c83924a2..3fece2925 100755
--- a/httemplate/browse/part_svc.cgi
+++ b/httemplate/browse/part_svc.cgi
@@ -53,12 +53,13 @@ function part_export_areyousure(href) {
<% foreach my $part_svc ( @part_svc ) {
my $hashref = $part_svc->hashref;
my $svcdb = $hashref->{svcdb};
- my @dfields = fields($svcdb);
+ my $svc_x = "FS::$svcdb"->new( { svcpart => $part_svc->svcpart } );
+ my @dfields = $svc_x->fields;
push @dfields, 'usergroup' if $svcdb eq 'svc_acct'; #kludge
my @fields =
- grep { $_ ne 'svcnum' && $part_svc->part_svc_column($_)->columnflag }
- @dfields;
-
+ grep { $svc_x->pvf($_)
+ or $_ ne 'svcnum' && $part_svc->part_svc_column($_)->columnflag }
+ @dfields ;
my $rowspan = scalar(@fields) || 1;
my $url = "${p}edit/part_svc.cgi?$hashref->{svcpart}";
%>
@@ -96,6 +97,7 @@ map { qsearchs('part_export', { exportnum => $_->exportnum } ) } qsearch('export
<% if ( $flag eq "D" ) { print "Default"; }
elsif ( $flag eq "F" ) { print "Fixed"; }
+ elsif ( not $flag ) { }
else { print "(Unknown!)"; }
%>
</TD><TD><%= $part_svc->part_svc_column($field)->columnvalue%></TD>
diff --git a/httemplate/browse/part_virtual_field.cgi b/httemplate/browse/part_virtual_field.cgi
new file mode 100644
index 000000000..9bb5c1373
--- /dev/null
+++ b/httemplate/browse/part_virtual_field.cgi
@@ -0,0 +1,39 @@
+<%= header('Virtual field definitions', menubar('Main Menu' => $p)) %>
+<%
+
+my %pvfs;
+my $block;
+my $p2 = popurl(2);
+my $dbtable;
+
+foreach (qsearch('part_virtual_field', {})) {
+ push @{ $pvfs{$_->dbtable} }, $_;
+}
+%>
+
+<% if ($cgi->param('error')) { %>
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT>
+ <BR><BR>
+<% } %>
+
+<A HREF="<%=$p2%>edit/part_virtual_field.cgi"><I>Add a new field</I></A><BR><BR>
+
+<% foreach $dbtable (sort { $a cmp $b } keys (%pvfs)) { %>
+<H3><%=$dbtable%></H3>
+
+<%=table()%>
+<TH><TD>Field name</TD><TD>Description</TD></TH>
+<% foreach $pvf (sort {$a->name cmp $b->name} @{ $pvfs{$dbtable} }) { %>
+ <TR>
+ <TD></TD>
+ <TD>
+ <A HREF="<%=$p2%>edit/part_virtual_field.cgi?<%=$pvf->vfieldpart%>">
+ <%=$pvf->name%></A></TD>
+ <TD><%=$pvf->label%></TD>
+ </TR>
+<% } %>
+</TABLE>
+<% } %>
+</BODY>
+</HTML>
+
diff --git a/httemplate/browse/router.cgi b/httemplate/browse/router.cgi
index 8864936d9..a9ec6fd61 100644
--- a/httemplate/browse/router.cgi
+++ b/httemplate/browse/router.cgi
@@ -14,21 +14,22 @@ my $p2 = popurl(2);
<A HREF="<%=$p2%>edit/router.cgi"><I>Add a new router</I></A><BR><BR>
<%=table()%>
-<!-- <TH><TD>Field name</TD><TD>Field value</TD></TH> -->
-<% foreach $router (sort {$a->routernum <=> $b->routernum} @router) { %>
<TR>
-<!-- <TD ROWSPAN="<%=scalar($router->router_field) + 2%>"> -->
- <TD>
+ <TD><B>Router name</B></TD>
+ <TD><B>Address block(s)</B></TD>
+ </TR>
+<% foreach $router (sort {$a->routernum <=> $b->routernum} @router) {
+ my @addr_block = $router->addr_block;
+%>
+ <TR>
+ <TD ROWSPAN="<%=scalar(@addr_block)%>">
<A HREF="<%=$p2%>edit/router.cgi?<%=$router->routernum%>"><%=$router->routername%></A>
</TD>
- <!--
- <% foreach (sort { $a->part_router_field->name cmp $b->part_router_field->name } $router->router_field ) { %>
- <TR>
- <TD BGCOLOR="#cccccc" ALIGN="right"><%=$_->part_router_field->name%></TD>
- <TD BGCOLOR="#ffffff"><%=$_->value%></TD>
- </TR>
- <% } %>
- -->
+ <TD>
+ <% foreach my $block ( @addr_block ) { %>
+ <%=$block->NetAddr%></BR>
+ <% } %>
+ </TD>
</TR>
<% } %>
</TABLE>
diff --git a/httemplate/docs/upgrade10.html b/httemplate/docs/upgrade10.html
index 1035510db..4c2c17b85 100644
--- a/httemplate/docs/upgrade10.html
+++ b/httemplate/docs/upgrade10.html
@@ -14,6 +14,24 @@ CREATE TABLE cust_bill_pkg_detail (
);
CREATE INDEX cust_bill_pkg_detail1 ON cust_bill_pkg_detail ( pkgnum, invnum );
+CREATE TABLE part_virtual_field (
+ vfieldpart int NOT NULL,
+ dbtable varchar(32) NOT NULL,
+ name varchar(32) NOT NULL,
+ check_block text,
+ list_source text,
+ length integer,
+ label varchar(80),
+ PRIMARY KEY (vfieldpart)
+);
+
+CREATE TABLE virtual_field (
+ recnum integer NOT NULL,
+ vfieldpart integer NOT NULL,
+ value varchar(128) NOT NULL,
+ PRIMARY KEY (vfieldpart, recnum)
+);
+
CREATE TABLE router (
routernum serial,
routername varchar(80),
@@ -26,22 +44,6 @@ CREATE TABLE part_svc_router (
routernum int NOT NULL
);
-CREATE TABLE part_router_field (
- routerfieldpart serial,
- name varchar(80),
- length int NOT NULL,
- check_block text,
- list_source text,
- PRIMARY KEY (routerfieldpart)
-);
-
-CREATE TABLE router_field (
- routerfieldpart int NOT NULL,
- routernum int NOT NULL,
- value varchar(128)
-);
-CREATE UNIQUE INDEX router_field1 ON router_field ( routerfieldpart, routernum );
-
CREATE TABLE addr_block (
blocknum serial,
routernum int NOT NULL,
@@ -51,24 +53,6 @@ CREATE TABLE addr_block (
);
CREATE UNIQUE INDEX addr_block1 ON addr_block ( blocknum, routernum );
-CREATE TABLE part_sb_field (
- sbfieldpart serial,
- svcpart int NOT NULL,
- name varchar(80) NOT NULL,
- length int NOT NULL,
- check_block text NULL,
- list_source text NULL,
- PRIMARY key (sbfieldpart)
-);
-CREATE UNIQUE INDEX part_sb_field1 ON part_sb_field ( sbfieldpart, svcpart );
-
-CREATE TABLE sb_field (
- sbfieldpart int NOT NULL,
- svcnum int NOT NULL,
- value varchar(128)
-);
-CREATE UNIQUE INDEX sb_field1 ON sb_field ( sbfieldpart, svcnum );
-
CREATE TABLE svc_broadband (
svcnum int NOT NULL,
blocknum int NOT NULL,
@@ -112,7 +96,7 @@ optionally:
mandatory again:
dbdef-create username
-create-history-tables username cust_bill_pkg_detail router part_svc_router part_router_field router_field addr_block part_sb_field sb_field svc_broadband
+create-history-tables username cust_bill_pkg_detail router part_svc_router addr_block svc_broadband
dbdef-create username
diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi
index d4bb4704c..45e0b6ffe 100755
--- a/httemplate/edit/part_svc.cgi
+++ b/httemplate/edit/part_svc.cgi
@@ -65,6 +65,9 @@ blank <B>slipip</B> as well as a fixed shell something like <B>/bin/true</B> or
<BR><BR>
<%
+
+my %vfields;
+
#these might belong somewhere else for other user interfaces
#pry need to eventually create stuff that's shared amount UIs
my %defs = (
@@ -109,29 +112,43 @@ my %defs = (
'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_charge' => {
+# 'amount' => 'amount',
+# },
+# 'svc_wo' => {
+# 'worker' => 'Worker',
+# '_date' => 'Date',
+# },
'svc_www' => {
#'recnum' => '',
#'usersvc' => '',
},
'svc_broadband' => {
- 'actypenum' => 'This is the actypenum that refers to the type of AC that can be provisioned for this service. This field must be set fixed.',
'speed_down' => 'Maximum download speed for this service in Kbps. 0 denotes unlimited.',
'speed_up' => 'Maximum upload speed for this service in Kbps. 0 denotes unlimited.',
- 'acnum' => 'acnum of a specific AC that this service is restricted to. Not required',
'ip_addr' => 'IP address. Leave blank for automatic assignment.',
- 'ip_netmask' => 'Mask length, aka. netmask bits. (Eg. 255.255.255.0 == 24)',
- 'mac_addr' => 'MAC address which is used by some ACs for access control. Specified by 6 colon seperated hex octets. (Eg. 00:00:0a:bc:1a:2b)',
- 'location' => 'Defines the physically location at which this service was installed. This is not necessarily the billing address',
+ 'blocknum' => 'Address block.',
},
);
+ foreach $svcdb (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
+
my @dbs = $hashref->{svcdb}
? ( $hashref->{svcdb} )
: qw( svc_acct svc_domain svc_forward svc_www svc_broadband );
@@ -157,7 +174,7 @@ my %defs = (
$html .= '<BR><BR>'. table().
table(). "<TR><TH COLSPAN=$columns>Exports</TH></TR><TR>";
foreach my $part_export ( @part_export ) {
- $html .= '<TD><INPUT TYPE="checkbox"'.
+ $html .= '<TD><INPUT TYPE="checbox"'.
' NAME="exportnum'. $part_export->exportnum. '" VALUE="1" ';
$html .= 'CHECKED'
if ( $clone || $part_svc->svcpart ) #null svcpart search causing error
@@ -202,19 +219,27 @@ my %defs = (
qq!<INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE="D"!.
' CHECKED'x($flag eq 'D'). ">Default ".
qq!<INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE="F"!.
- ' CHECKED'x($flag eq 'F'). ">Fixed ".
- '<BR>';
+ ' CHECKED'x($flag eq 'F'). ">Fixed ";
+ $html .= '<BR>';
}
if ( ref($def) ) {
if ( $def->{type} eq 'select' ) {
$html .= qq!<SELECT NAME="${layer}__${field}">!;
$html .= '<OPTION> </OPTION>' unless $value;
- foreach my $record ( qsearch( $def->{select_table}, {} ) ) {
- my $rvalue = $record->getfield($def->{select_key});
- $html .= qq!<OPTION VALUE="$rvalue"!.
- ( $rvalue==$value ? ' SELECTED>' : '>' ).
- $record->getfield($def->{select_label}). '</OPTION>';
- }
+ if ( $def->{select_table} ) {
+ foreach my $record ( qsearch( $def->{select_table}, {} ) ) {
+ my $rvalue = $record->getfield($def->{select_key});
+ $html .= qq!<OPTION VALUE="$rvalue"!.
+ ( $rvalue==$value ? ' SELECTED>' : '>' ).
+ $record->getfield($def->{select_label}). '</OPTION>';
+ } #next $record
+ } else { # select_list
+ foreach my $item ( @{$def->{select_list}} ) {
+ $html .= qq!<OPTION VALUE="$item"!.
+ ( $item eq $value ? ' SELECTED>' : '>' ).
+ $item. '</OPTION>';
+ } #next $item
+ } #endif
$html .= '</SELECT>';
} elsif ( $def->{type} eq 'radius_usergroup_selector' ) {
$html .= FS::svc_acct::radius_usergroup_selector(
@@ -229,6 +254,11 @@ my %defs = (
$html .=
qq!<INPUT TYPE="text" NAME="${layer}__${field}" VALUE="$value">!;
}
+
+ if($vfields{$layer}->{$field}) {
+ $html .= qq!<BR><INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE="X"!.
+ ' CHECKED'x($flag eq 'X'). ">Excluded ";
+ }
$html .= "</TD></TR>\n";
}
$part_svc->svcpart('') if $clone; #undone
diff --git a/httemplate/edit/part_virtual_field.cgi b/httemplate/edit/part_virtual_field.cgi
new file mode 100644
index 000000000..8bc84387f
--- /dev/null
+++ b/httemplate/edit/part_virtual_field.cgi
@@ -0,0 +1,92 @@
+<!-- mason kludge -->
+<%
+my ($vfieldpart, $part_virtual_field);
+
+if ( $cgi->param('error') ) {
+ $part_virtual_field = new FS::part_virtual_field ( {
+ map { $_, scalar($cgi->param($_)) } fields('part_virtual_field')});
+ $vfieldpart = $part_virtual_field->vfieldpart;
+} else {
+ my($query) = $cgi->keywords;
+ if ( $query =~ /^(\d+)$/ ) { #editing
+ $vfieldpart=$1;
+ $part_virtual_field=qsearchs('part_virtual_field',
+ {'vfieldpart' => $vfieldpart})
+ or die "Unknown vfieldpart!";
+
+ } else { #adding
+ $part_virtual_field = new FS::part_virtual_field({});
+ }
+}
+my $action = $part_virtual_field->vfieldpart ? 'Edit' : 'Add';
+
+my $p1 = popurl(1);
+print header("$action Virtual Field Definition", '');
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+%>
+<FORM ACTION="<%=$p1%>process/generic.cgi" METHOD="POST">
+
+<INPUT TYPE="hidden" NAME="table" VALUE="part_virtual_field">
+<INPUT TYPE="hidden" NAME="redirect_ok"
+ VALUE="<%=popurl(2)%>browse/part_virtual_field.cgi">
+<INPUT TYPE="hidden" NAME="vfieldpart" VALUE="<%=
+ $vfieldpart%>">
+Field #<B><%=$vfieldpart or "(NEW)"%></B><BR><BR>
+
+<%=ntable("#cccccc",2)%>
+ <TR>
+ <TD ALIGN="right">Name</TD>
+ <TD><INPUT TYPE="text" NAME="name" MAXLENGTH=15 VALUE="<%=
+ $part_virtual_field->name%>"></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Table</TD>
+ <TD><% if ($action eq 'Add') { %>
+ <SELECT SIZE=1 NAME="dbtable"><%
+ my $dbdef = dbdef; # ick
+ foreach my $dbtable ($dbdef->tables) {
+ if ($dbtable !~ /^h_/
+ and $dbdef->table($dbtable)->primary_key) { %>
+ <OPTION VALUE="<%=$dbtable%>"><%=$dbtable%></OPTION><%
+ }
+ }
+ %></SELECT><%
+ } else { # Edit
+ %><%=$part_virtual_field->dbtable%>
+ <INPUT TYPE="hidden" NAME="dbtable" VALUE="<%=$part_virtual_field->dbtable%>">
+ <% } %>
+ </TD>
+ <TR>
+ <TD ALIGN="right">Label</TD>
+ <TD><INPUT TYPE="text" NAME="label" MAXLENGTH="20" VALUE="<%=
+ $part_virtual_field->label%>"></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Length</TD>
+ <TD><INPUT TYPE="text" NAME="length" MAXLENGTH=4 VALUE="<%=
+ $part_virtual_field->length%>"></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Check</TD>
+ <TD><TEXTAREA COLS="20" ROWS="4" NAME="check_block"><%=
+ $part_virtual_field->check_block%></TEXTAREA></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">List source</TD>
+ <TD><TEXTAREA COLS="20" ROWS="4" NAME="list_source"><%=
+ $part_virtual_field->list_source%></TEXTAREA></TD>
+ </TR>
+</TABLE><BR><INPUT TYPE="submit" VALUE="Submit">
+
+</FORM>
+
+<BR><BR>
+<FONT SIZE=-2>If you don't understand what <I>check_block</I> and
+<I>list_source</I> mean, <B>LEAVE THEM BLANK</B>. We mean it.</FONT>
+
+
+</BODY>
+</HTML>
diff --git a/httemplate/edit/process/router.cgi b/httemplate/edit/process/router.cgi
index 1b7fc3805..7e40c48a7 100644
--- a/httemplate/edit/process/router.cgi
+++ b/httemplate/edit/process/router.cgi
@@ -19,19 +19,16 @@ my $error = '';
my $routernum = $cgi->param('routernum');
my $routername = $cgi->param('routername');
my $old = qsearchs('router', { routernum => $routernum });
-my @old_rf;
my @old_psr;
my $new = new FS::router {
- routernum => $routernum,
- routername => $routername,
- svcnum => 0
- };
+ map {
+ ($_, scalar($cgi->param($_)));
+ } fields('router')
+};
if($old) {
- if($old->routername ne $new->routername) {
- $error = $new->replace($old);
- } #else do nothing
+ $error = $new->replace($old);
} else {
$error = $new->insert;
$routernum = $new->routernum;
@@ -49,21 +46,6 @@ if ($old) {
}
}
check($error);
- @old_rf = $old->router_field;
- foreach $rf (@old_rf) {
- if(my $new_val = $cgi->param('rf_'.$rf->routerfieldpart)) {
- if($new_val ne $rf->value) {
- my $new_rf = new FS::router_field
- { routernum => $routernum,
- value => $new_val,
- routerfieldpart => $rf->routerfieldpart };
- $error = $new_rf->replace($rf);
- } #else do nothing
- } else {
- $error = $rf->delete;
- }
- check($error);
- }
}
foreach($cgi->param) {
@@ -77,23 +59,10 @@ foreach($cgi->param) {
$error = $new_psr->insert;
}
check($error);
- } elsif($cgi->param($_) ne '' and /^rf_(\d+)$/) {
- my $part = $1;
- if(my @x = grep {$_->routerfieldpart == $part} @old_rf) {
- # already handled all of these
- } else {
- my $new_rf = new FS::router_field
- { routernum => $routernum,
- value => $cgi->param('rf_'.$part),
- routerfieldpart => $part };
- $error = $new_rf->insert;
- check($error);
- }
}
}
-
# Yay, everything worked!
$dbh->commit or die $dbh->errstr;
print $cgi->redirect(popurl(3). "browse/router.cgi");
diff --git a/httemplate/edit/process/svc_broadband.cgi b/httemplate/edit/process/svc_broadband.cgi
index ab8b9f9d8..4912a3a9f 100644
--- a/httemplate/edit/process/svc_broadband.cgi
+++ b/httemplate/edit/process/svc_broadband.cgi
@@ -9,11 +9,10 @@ my $dbh = FS::UID::dbh;
$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
my $svcnum = $1;
-my $old; my @old_sbf;
+my $old;
if ( $svcnum ) {
$old = qsearchs('svc_broadband', { 'svcnum' => $svcnum } )
or die "fatal: can't find broadband service (svcnum $svcnum)!";
- @old_sbf = $old->sb_field;
} else {
$old = '';
}
@@ -32,39 +31,6 @@ if ( $svcnum ) {
$svcnum = $new->svcnum;
}
-unless ($error) {
- my $sb_field;
-
- foreach ($cgi->param) {
- #warn "\$cgi->param $_: " . $cgi->param($_);
- if(/^sbf_(\d+)/) {
- my $part = $1;
- #warn "\$part $part";
- $sb_field = new FS::sb_field
- { svcnum => $svcnum,
- value => $cgi->param($_),
- sbfieldpart => $part };
- if (my @x = grep { $_->sbfieldpart eq $part } @old_sbf) {
- #if (my $old_sb_field = (grep { $_->sbfieldpart eq $part} @old_Sbf)[0]) {
- #warn "array: " . scalar(@x);
- if (length($sb_field->value) && ($sb_field->value ne $x[0]->value)) {
- #warn "replacing " . $x[0]->value . " with " . $sb_field->value;
- $error = $sb_field->replace($x[0]);
- #$error = $sb_field->replace($old_sb_field);
- } elsif (length($sb_field->value) == 0) {
- #warn "delete";
- $error = $x[0]->delete;
- }
- } else {
- if (length($sb_field->value) > 0) {
- #warn "insert";
- $error = $sb_field->insert;
- }
- # else do nothing
- }
- }
- }
-}
if ( $error ) {
$cgi->param('error', $error);
diff --git a/httemplate/edit/router.cgi b/httemplate/edit/router.cgi
index b524c6494..5c365a21e 100755
--- a/httemplate/edit/router.cgi
+++ b/httemplate/edit/router.cgi
@@ -16,7 +16,6 @@ if ( $cgi->keywords ) {
my $routernum = $router->routernum;
my $action = $routernum ? 'Edit' : 'Add';
-my $hashref = $router->hashref;
print header("$action Router", menubar(
'Main Menu' => "$p",
@@ -28,10 +27,13 @@ if($cgi->param('error')) {
<% } %>
<FORM ACTION="<%=popurl(1)%>process/router.cgi" METHOD=POST>
+ <INPUT TYPE="hidden" NAME="table" VALUE="router">
+ <INPUT TYPE="hidden" NAME="redirect_ok" VALUE="<%=$p3%>/browse/router.cgi">
+ <INPUT TYPE="hidden" NAME="redirect_error" VALUE="<%=$p3%>/edit/router.cgi">
<INPUT TYPE="hidden" NAME="routernum" VALUE="<%=$routernum%>">
Router #<%=$routernum or "(NEW)"%>
-<BR><BR>Name <INPUT TYPE="text" NAME="routername" SIZE=32 VALUE="<%=$hashref->{routername}%>">
+<BR><BR>Name <INPUT TYPE="text" NAME="routername" SIZE=32 VALUE="<%=$router->routername%>">
<BR><BR>
Custom fields:
@@ -39,35 +41,11 @@ Custom fields:
<%=table() %>
<%
-# I know, I know. Massive false laziness with edit/svc_broadband.cgi. But
-# Kristian won't let me generalize the custom field mechanism to every table in
-# the database, so this is what we get. <snarl>
-# -- MW
-
-my @part_router_field = qsearch('part_router_field', { });
-my %rf = map { $_->part_router_field->name, $_->value } $router->router_field;
-foreach (sort { $a->name cmp $b->name } @part_router_field) {
- %>
- <TR>
- <TD ALIGN="right"><%=$_->name%></TD>
- <TD><%
- if(my @opts = $_->list_values) {
- %> <SELECT NAME="rf_<%=$_->routerfieldpart%>" SIZE="1">
- <%
- foreach $opt (@opts) {
- %> <OPTION VALUE="<%=$opt%>"<%=($opt eq $rf{$_->name})
- ? ' SELECTED' : ''%>>
- <%=$opt%>
- </OPTION>
- <% } %>
- </SELECT>
- <% } else { %>
- <INPUT NAME="rf_<%=$_->routerfieldpart%>"
- VALUE="<%=$rf{$_->name}%>"
- <%=$_->length ? 'SIZE="'.$_->length.'"' : ''%>>
- <% } %></TD>
- </TR>
-<% } %>
+foreach my $field ($router->virtual_fields) {
+ print $router->pvf($field)->widget('HTML', 'edit',
+ $router->getfield($field));
+}
+%>
</TABLE>
@@ -81,7 +59,7 @@ foreach my $part_svc ( qsearch('part_svc', { svcdb => 'svc_broadband',
<BR>
<INPUT TYPE="checkbox" NAME="svcpart_<%=$part_svc->svcpart%>"<%=
qsearchs('part_svc_router', { svcpart => $part_svc->svcpart,
- routernum => $routernum } ) ? 'CHECKED' : ''%> VALUE="ON">
+ routernum => $routernum } ) ? ' CHECKED' : ''%> VALUE="ON">
<A HREF="<%=${p}%>edit/part_svc.cgi?<%=$part_svc->svcpart%>">
<%=$part_svc->svcpart%>: <%=$part_svc->svc%></A>
<% } %>
diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi
index 4420bb609..44606d9a0 100755
--- a/httemplate/edit/svc_acct.cgi
+++ b/httemplate/edit/svc_acct.cgi
@@ -35,8 +35,6 @@ if ( $cgi->param('error') ) {
} else { #adding
- $svc_acct = new FS::svc_acct({});
-
foreach $_ (split(/-/,$query)) {
$pkgnum=$1 if /^pkgnum(\d+)$/;
$svcpart=$1 if /^svcpart(\d+)$/;
@@ -44,6 +42,8 @@ if ( $cgi->param('error') ) {
$part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
die "No part_svc entry for svcpart $svcpart!" unless $part_svc;
+ $svc_acct = new FS::svc_acct({svcpart => $svcpart});
+
$svcnum='';
#set gecos
@@ -281,6 +281,14 @@ if ( $part_svc->part_svc_column('usergroup')->columnflag eq "F" ) {
}
print '</TD></TR>';
+foreach $field ($svc_acct->virtual_fields) {
+ if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) {
+ # If the flag is X, it won't even show up in $svc_acct->virtual_fields.
+ print $svc_acct->pvf($field)->widget('HTML', 'edit',
+ $svc_acct->getfield($field));
+ }
+}
+
#submit
print qq!</TABLE><BR><INPUT TYPE="submit" VALUE="Submit">!;
diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi
index ee7f8becf..6828791c0 100644
--- a/httemplate/edit/svc_broadband.cgi
+++ b/httemplate/edit/svc_broadband.cgi
@@ -36,8 +36,6 @@ if ( $cgi->param('error') ) {
} else { #adding
- $svc_broadband = new FS::svc_broadband({});
-
foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart
$pkgnum=$1 if /^pkgnum(\d+)$/;
$svcpart=$1 if /^svcpart(\d+)$/;
@@ -45,7 +43,7 @@ if ( $cgi->param('error') ) {
$part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
die "No part_svc entry!" unless $part_svc;
- $svc_broadband->setfield('svcpart', $svcpart);
+ $svc_broadband = new FS::svc_broadband({ svcpart => $svcpart });
$svcnum='';
@@ -159,30 +157,12 @@ Service #<B><%=$svcnum ? $svcnum : "(NEW)"%></B><BR><BR>
<% } %>
<%
-
- my @part_sb_field = qsearch('part_sb_field', { svcpart => $svcpart });
- my $sbf_hashref = $svc_broadband->sb_field_hashref($svcpart);
- foreach (sort { $a->name cmp $b->name } @part_sb_field) {
- %>
- <TR>
- <TD ALIGN="right"><%=$_->name%></TD>
- <TD><%
- if(my @opts = $_->list_values) {
- %>
- <SELECT NAME="sbf_<%=$_->sbfieldpart%>" SIZE=1> <%
- foreach $opt (@opts) { %>
- <OPTION VALUE="<%=$opt%>"<%=
- ($opt eq $sbf_hashref->{$_->name}) ? ' SELECTED' : ''%>>
- <%=$opt%></OPTION><%
- } %></SELECT>
- <% } else { %>
- <INPUT NAME="sbf_<%=$_->sbfieldpart%>"
- VALUE="<%=$sbf_hashref->{$_->name}%>"
- <%=$_->length ? 'SIZE="'.$_->length.'"' : ''%>>
- <% } %>
- </TD>
- </TR>
-<% } %>
+foreach $field ($svc_broadband->virtual_fields) {
+ if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) {
+ print $svc_broadband->pvf($field)->widget('HTML', 'edit',
+ $svc_broadband->getfield($field));
+ }
+} %>
</TABLE>
<BR>
<INPUT TYPE="submit" NAME="submit" VALUE="Submit">
diff --git a/httemplate/edit/svc_www.cgi b/httemplate/edit/svc_www.cgi
index d2c9ade5c..043af610b 100644
--- a/httemplate/edit/svc_www.cgi
+++ b/httemplate/edit/svc_www.cgi
@@ -29,12 +29,12 @@ if ( $cgi->param('error') ) {
} else { #adding
- $svc_www = new FS::svc_www({});
-
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 };
+
$part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
die "No part_svc entry!" unless $part_svc;
@@ -167,6 +167,14 @@ foreach $_ (keys %username) {
}
print "</SELECT></TD></TR>";
+foreach $field ($svc_www->virtual_fields) {
+ if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) {
+ # If the flag is X, it won't even show up in $svc_acct->virtual_fields.
+ print $svc_www->pvf($field)->widget('HTML', 'edit',
+ $svc_www->getfield($field));
+ }
+}
+
print '</TABLE><BR><INPUT TYPE="submit" VALUE="Submit">';
print <<END;
diff --git a/httemplate/index.html b/httemplate/index.html
index 017ffcd88..1895f2912 100644
--- a/httemplate/index.html
+++ b/httemplate/index.html
@@ -11,7 +11,7 @@
</td><td>
<font color="#ff0000" size=7>freeside main menu</font>
</td><td align=right valign=bottom>
- version %%%VERSION%%%
+ version 1.5.0pre2
<BR><A HREF="http://www.sisd.com/freeside">Freeside home page</A>
<BR><A HREF="docs/">Documentation</A>
</td></tr>
@@ -199,10 +199,8 @@
- Points of Presence
<LI><A HREF="browse/part_bill_event.cgi">View/Edit invoice events</A> - Actions for overdue invoices
<LI><A HREF="browse/msgcat.cgi">View/Edit message catalog</A> - Change error messages and other customizable labels.
- <LI><A HREF="browse/part_sb_field.cgi">View/Edit custom svc_broadband fields</A>
- - Custom broadband service fields for site-specific export/informational data.
- <LI><A HREF="browse/generic.cgi?part_router_field">View/Edit custom router fields</A>
- - Custom router fields for site-specific export data.
+ <LI><A HREF="browse/part_virtual_field.cgi">View/Edit virtual fields</A>
+ - Locally defined fields
<LI><A HREF="browse/router.cgi">View/Edit routers</A>
- Broadband access routers
<LI><A HREF="browse/addr_block.cgi">View/Edit address blocks</A>
diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi
index 599c1d8b9..4e01db341 100755
--- a/httemplate/view/svc_acct.cgi
+++ b/httemplate/view/svc_acct.cgi
@@ -172,7 +172,7 @@ if ($svc_acct->slipip) {
: $svc_acct->slipip
). "</TD>";
my($attribute);
- foreach $attribute ( grep /^radius_/, fields('svc_acct') ) {
+ foreach $attribute ( grep /^radius_/, $svc_acct->fields ) {
#warn $attribute;
$attribute =~ /^radius_(.*)$/;
my $pattribute = $FS::raddb::attrib{$1};
@@ -180,7 +180,7 @@ if ($svc_acct->slipip) {
"<TD BGCOLOR=\"#ffffff\">". $svc_acct->getfield($attribute).
"</TD></TR>";
}
- foreach $attribute ( grep /^rc_/, fields('svc_acct') ) {
+ foreach $attribute ( grep /^rc_/, $svc_acct->fields ) {
#warn $attribute;
$attribute =~ /^rc_(.*)$/;
my $pattribute = $FS::raddb::attrib{$1};
@@ -195,7 +195,19 @@ if ($svc_acct->slipip) {
print '<TR><TD ALIGN="right">RADIUS groups</TD><TD BGCOLOR="#ffffff">'.
join('<BR>', $svc_acct->radius_groups). '</TD></TR>';
-print '</TABLE></TD></TR></TABLE><BR><BR>';
+# Can this be abstracted further? Maybe a library function like
+# widget('HTML', 'view', $svc_acct) ? It would definitely make UI
+# style management easier.
+
+foreach (sort { $a cmp $b } $svc_acct->virtual_fields) {
+ print $svc_acct->pvf($_)->widget('HTML', 'view', $svc_acct->getfield($_)),
+ "\n";
+}
+%>
+</TABLE></TD></TR></TABLE>
+<%
+
+print '<BR><BR>';
print join("\n", $conf->config('svc_acct-notes') ). '<BR><BR>'.
joblisting({'svcnum'=>$svcnum}, 1). '</BODY></HTML>';
diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi
index 164b5b22c..677a4b058 100644
--- a/httemplate/view/svc_broadband.cgi
+++ b/httemplate/view/svc_broadband.cgi
@@ -64,26 +64,12 @@ print header('Broadband Service View', menubar(
qq!<TD BGCOLOR="#ffffff">$ip_addr</TD></TR>!.
'</TD></TR><TR ROWSPAN="1"><TD></TD></TR>';
+foreach (sort { $a cmp $b } $svc_broadband->virtual_fields) {
+ print $svc_broadband->pvf($_)->widget('HTML', 'view',
+ $svc_broadband->getfield($_)), "\n";
+}
-# foreach my $sb_field
-# ( qsearch('sb_field', { svcnum => $svcnum }) ) {
-# my $part_sb_field = qsearchs('part_sb_field',
-# { sbfieldpart => $sb_field->sbfieldpart });
-# print q!<TR><TD ALIGN="right">! . $part_sb_field->name .
-# q!</TD><TD BGCOLOR="#ffffff">! . $sb_field->value .
-# q!</TD></TR>!;
-# }
-# print '</TABLE>';
-
-
- my $sb_field = $svc_broadband->sb_field_hashref;
- foreach (sort { $a cmp $b } keys(%{$sb_field})) {
- print q!<TR><TD ALIGN="right">! . $_ .
- q!</TD><TD BGCOLOR="#ffffff">! . $sb_field->{$_} .
- q!</TD></TR>!;
- }
- print '</TABLE>';
-
+print '</TABLE>';
print '<BR>'. joblisting({'svcnum'=>$svcnum}, 1).
'</BODY></HTML>'
diff --git a/httemplate/view/svc_forward.cgi b/httemplate/view/svc_forward.cgi
index c8d1d6213..5d619de54 100755
--- a/httemplate/view/svc_forward.cgi
+++ b/httemplate/view/svc_forward.cgi
@@ -61,7 +61,14 @@ print qq!<A HREF="${p}edit/svc_forward.cgi?$svcnum">Edit this information</A>!.
qq!<TR><TD ALIGN="right">Email to</TD>!.
qq!<TD BGCOLOR="#ffffff">$source</TD></TR>!.
qq!<TR><TD ALIGN="right">Forwards to </TD>!.
- qq!<TD BGCOLOR="#ffffff">$destination</TD></TR></TABLE>!.
+ qq!<TD BGCOLOR="#ffffff">$destination</TD></TR>!;
+
+foreach (sort { $a cmp $b } $svc_forward->virtual_fields) {
+ print $svc_forward->pvf($_)->widget('HTML', 'view', $svc_forward->getfield($_)),
+ "\n";
+}
+
+print qq! </TABLE>!.
'<BR>'. joblisting({'svcnum'=>$svcnum}, 1).
'</BODY></HTML>'
;
diff --git a/httemplate/view/svc_www.cgi b/httemplate/view/svc_www.cgi
index 442614491..7a716b4b8 100644
--- a/httemplate/view/svc_www.cgi
+++ b/httemplate/view/svc_www.cgi
@@ -47,8 +47,15 @@ print header('Website View', menubar(
qq!<TR><TD ALIGN="right">Website name</TD>!.
qq!<TD BGCOLOR="#ffffff"><A HREF="http://$www">$www<A></TD></TR>!.
qq!<TR><TD ALIGN="right">Account</TD>!.
- qq!<TD BGCOLOR="#ffffff"><A HREF="${p}view/svc_acct.cgi?$usersvc">$email</A></TD></TR>!.
- '</TABLE></TD></TR></TABLE>'.
+ qq!<TD BGCOLOR="#ffffff"><A HREF="${p}view/svc_acct.cgi?$usersvc">$email</A></TD></TR>!;
+
+foreach (sort { $a cmp $b } $svc_www->virtual_fields) {
+ print $svc_www->pvf($_)->widget('HTML', 'view', $svc_www->getfield($_)),
+ "\n";
+}
+
+
+print '</TABLE></TD></TR></TABLE>'.
'<BR>'. joblisting({'svcnum'=>$svcnum}, 1).
'</BODY></HTML>'
;