From a61b1651b6cee90f6195d8ac59da657d8966567d Mon Sep 17 00:00:00 2001 From: khoff Date: Mon, 4 Aug 2003 23:47:48 +0000 Subject: Virtual field documentation updates. Removed sb_field/router_field and friends (yay!). Interface changes to support virtual fields. --- FS/FS/part_router_field.pm | 134 ------------- FS/FS/part_sb_field.pm | 267 -------------------------- FS/FS/part_virtual_field.pm | 303 ++++++++++++++++++++++++++++++ FS/FS/router_field.pm | 146 -------------- FS/FS/sb_field.pm | 148 --------------- FS/MANIFEST | 5 +- FS/bin/freeside-setup | 49 ----- htetc/global.asa | 5 +- htetc/handler.pl | 5 +- htetc/handler.pl-1.0x | 5 +- httemplate/browse/part_sb_field.cgi | 31 --- httemplate/browse/part_virtual_field.cgi | 39 ++++ httemplate/docs/upgrade10.html | 54 ++---- httemplate/edit/part_router_field.cgi | 71 ------- httemplate/edit/part_sb_field.cgi | 79 -------- httemplate/edit/part_virtual_field.cgi | 92 +++++++++ httemplate/edit/process/router.cgi | 41 +--- httemplate/edit/process/svc_broadband.cgi | 36 +--- httemplate/edit/router.cgi | 4 +- httemplate/view/svc_acct.cgi | 5 - httemplate/view/svc_broadband.cgi | 21 +-- 21 files changed, 466 insertions(+), 1074 deletions(-) delete mode 100755 FS/FS/part_router_field.pm delete mode 100755 FS/FS/part_sb_field.pm create mode 100755 FS/FS/part_virtual_field.pm delete mode 100755 FS/FS/router_field.pm delete mode 100755 FS/FS/sb_field.pm delete mode 100644 httemplate/browse/part_sb_field.cgi create mode 100644 httemplate/browse/part_virtual_field.cgi delete mode 100644 httemplate/edit/part_router_field.cgi delete mode 100644 httemplate/edit/part_sb_field.cgi create mode 100644 httemplate/edit/part_virtual_field.cgi diff --git a/FS/FS/part_router_field.pm b/FS/FS/part_router_field.pm deleted file mode 100755 index 73ca50f..0000000 --- a/FS/FS/part_router_field.pm +++ /dev/null @@ -1,134 +0,0 @@ -package FS::part_router_field; - -use strict; -use vars qw( @ISA ); -use FS::Record qw( qsearchs ); -use FS::router_field; -use FS::router; - - -@ISA = qw( FS::Record ); - -=head1 NAME - -FS::part_router_field - Object methods for part_router_field records - -=head1 SYNOPSIS - - use FS::part_router_field; - - $record = new FS::part_router_field \%hash; - $record = new FS::part_router_field { 'column' => 'value' }; - - $error = $record->insert; - - $error = $new_record->replace($old_record); - - $error = $record->delete; - - $error = $record->check; - -=head1 DESCRIPTION - -A part_router_field represents an xfield definition for routers. For more -information on xfields, see L. - -The following fields are supported: - -=over 4 - -=item routerfieldpart - primary key (assigned automatically) - -=item name - name of field - -=item length - -=item check_block - -=item list_source - -(See L for details on these fields.) - -=head1 METHODS - -=over 4 - -=item new HASHREF - -Create a new record. To add the record to the database, see "insert". - -=cut - -sub table { 'part_router_field'; } - -=item insert - -Adds this record to the database. If there is an error, returns the error, -otherwise returns false. - -=item delete - -Deletes this record from the database. If there is an error, returns the -error, otherwise returns false. - -=item replace OLD_RECORD - -Replaces OLD_RECORD with this one in the database. If there is an error, -returns the error, otherwise returns false. - -=item check - -Checks all fields to make sure this is a valid record. If there is an error, -returns the error, otherwise returns false. Called by the insert and replace -methods. - -=cut - -sub check { - my $self = shift; - my $error = ''; - - $self->name =~ /^([a-z0-9_\-\.]{1,15})$/i - or return "Invalid field name for part_router_field"; - - ''; #no error -} - -=item list_values - -Equivalent to "eval($part_router_field->list_source)". - -=cut - -sub list_values { - my $self = shift; - return () unless $self->list_source; - my @opts = eval($self->list_source); - if($@) { - warn $@; - return (); - } else { - return @opts; - } -} - -=back - -=head1 VERSION - -$Id: - -=head1 BUGS - -Needless duplication of much of FS::part_sb_field, with the result that most of -the warnings about it apply here also. - -=head1 SEE ALSO - -FS::svc_broadband, FS::router, FS::router_field, schema.html -from the base documentation. - -=cut - -1; - diff --git a/FS/FS/part_sb_field.pm b/FS/FS/part_sb_field.pm deleted file mode 100755 index 8dca946..0000000 --- a/FS/FS/part_sb_field.pm +++ /dev/null @@ -1,267 +0,0 @@ -package FS::part_sb_field; - -use strict; -use vars qw( @ISA ); -use FS::Record qw( qsearchs ); - -@ISA = qw( FS::Record ); - -=head1 NAME - -FS::part_sb_field - Object methods for part_sb_field records - -=head1 SYNOPSIS - - use FS::part_sb_field; - - $record = new FS::part_sb_field \%hash; - $record = new FS::part_sb_field { 'column' => 'value' }; - - $error = $record->insert; - - $error = $new_record->replace($old_record); - - $error = $record->delete; - - $error = $record->check; - -=head1 DESCRIPTION - -An FS::part_sb_field object represents an extended field (xfield) definition -for svc_broadband's sb_field mechanism (see L). -FS::part_sb_field inherits from FS::Record. The following fields are -currently supported: - -=over 2 - -=item sbfieldpart - primary key (assigned automatically) - -=item name - name of the field - -=item svcpart - service type for which this field is available (see L) - -=item length - length of the contents of the field (see note #1) - -=item check_block - validation routine (see note #2) - -=item list_source - enumeration routine (see note #3) - -=back - -=head1 BACKGROUND - -Broadband services, unlike dialup services, are provided over a wide -variety of physical media (DSL, wireless, cable modems, digital circuits) -and network architectures (Ethernet, PPP, ATM). For many of these access -mechanisms, adding a new customer requires knowledge of some properties -of the physical connection (circuit number, the type of CPE in use, etc.). -It is unreasonable to expect ISPs to alter Freeside's schema (and the -associated library and UI code) to make each of these parameters a field in -svc_broadband. - -Hence sb_field and part_sb_field. They allow the Freeside administrator to -define 'extended fields' ('xfields') associated with svc_broadband records. -These are I processed in any way by Freeside itself; they exist solely for -use by exports (see L) and technical support staff. - -For a parallel mechanism (at the per-router level rather than per-service), -see L. - -=head1 METHODS - -=over 4 - -=item new HASHREF - -Create a new record. To add the record to the database, see "insert". - -=cut - -sub table { 'part_sb_field'; } - -=item insert - -Adds this record to the database. If there is an error, returns the error, -otherwise returns false. - -=item delete - -Deletes this record from the database. If there is an error, returns the -error, otherwise returns false. - -=item replace OLD_RECORD - -Replaces OLD_RECORD with this one in the database. If there is an error, -returns the error, otherwise returns false. - -=item check - -Checks all fields to make sure this is a valid record. If there is an error, -returns the error, otherwise returns false. Called by the insert and replace -methods. - -=cut - -sub check { - my $self = shift; - my $error = ''; - - $error = $self->ut_numbern('svcpart'); - return $error if $error; - - unless (qsearchs('part_svc', { svcpart => $self->svcpart })) - { return "Unknown svcpart: " . $self->svcpart;} - - $self->name =~ /^([a-z0-9_\-\.]{1,15})$/i - or return "Invalid field name for part_sb_field"; - - #How to check input_block, display_block, and check_block? - - ''; #no error -} - -=item list_values - -If the I field is set, this method eval()s it and -returns its output. If the field is empty, list_values returns -an empty list. - -Any arguments passed to this method will be received by the list_source -code, but this behavior is a fortuitous accident and may be removed in -the future. - -=cut - -sub list_values { - my $self = shift; - return () unless $self->list_source; - - my @opts = eval($self->list_source); - if($@) { - warn $@; - return (); - } else { - return @opts; - } -} - -=item part_svc - -Returns the FS::part_svc object associated with this field definition. - -=cut - -sub part_svc { - my $self = shift; - return qsearchs('part_svc', { svcpart => $self->svcpart }); -} - -=back - -=head1 VERSION - -$Id: - -=head1 NOTES - -=over - -=item 1. - -The I field is not enforced. It provides a hint to UI -code about how to display the field on a form. If you want to enforce a -minimum or maximum length for a field, use a I. - -=item 2. - -The check_block mechanism used here as well as in -FS::part_router_field allows the user to define validation rules. - -When FS::sb_field::check is called, the proposed value of the xfield is -assigned to $_. The check_block is then eval()'d and its return value -captured. If the return value is false (empty/zero/undef), $_ is then assigned -back into the field and stored in the database. - -Therefore a check_block can do three different things with the value: allow -it, allow it with a modification, or reject it. This is very flexible, but -somewhat dangerous. Some warnings: - -=over 2 - -=item * - -Assume that $_ has had I error checking prior to the -check_block. That's what the check_block is for, after all. It could -contain I: evil shell commands in backquotes, 100kb JPEG images, -the Klez virus, whatever. - -=item * - -If your check_block modifies the input value, it should probably -produce a value that wouldn't be modified by going through the same -check_block again. (That is, it should map input values into its own -eigenspace.) The reason is that if someone calls $new->replace($old), -where $new and $old contain the same value for the field, they probably -want the field to keep its old value, not to get transformed by the -check_block again. So don't do silly things like '$_++' or -'tr/A-Za-z/a-zA-Z/'. - -=item * - -Don't alter the contents of the database. I the database -is perfectly reasonable, but writing to it is a bad idea. Remember that -check() might get called more than once, as described above. - -=item * - -The check_block probably won't even get called if the user submits -an I sb_field. So at present, you can't set up a default value with -something like 's/^$/foo/'. Conversely, don't replace the submitted value -with an empty string. It probably will get stored, but might be deleted at -any time. - -=back - -=item 3. - -The list_source mechanism is a UI hint (like length) to generate -drop-down or list boxes. If list_source contains a value, the UI code can -eval() it and use the results as the options on the list. - -Note 'can'. This is not a substitute for check_block. The HTML interface -currently requires that the user pick one of the options on the list -because that's the way HTML drop-down boxes work, but in the future the UI -code might add an 'Other (please specify)' option and a text box so that -the user can enter something else. Or it might ignore list_source and just -generate a text box. Or the interface might be rewritten in MS Access, -where drop-down boxes have text boxes built in. Data validation is the job -of check(), not the front end. - -Note also that a list of literals evaluates to itself, so a list_source -like - -C<('Windows', 'MacOS', 'Linux')> - -or - -C - -means exactly what you'd think. - -=head1 BUGS - -The lack of any way to do default values. We might add this as another UI -hint (since, for the most part, it's the UI's job to figure out which fields -have had values entered into them). In fact, there are lots of things we -should add as UI hints. - -Oh, and the documentation is probably full of lies. - -=head1 SEE ALSO - -FS::svc_broadband, FS::sb_field, schema.html from the base documentation. - -=cut - -1; - diff --git a/FS/FS/part_virtual_field.pm b/FS/FS/part_virtual_field.pm new file mode 100755 index 0000000..3283f6a --- /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 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 +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 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 +LabelValue + +(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!! . $label . + q!! . $value . + q!! . "\n"; + } elsif ($mode eq 'edit') { + $text = q!! . $label . + q!!; + if ($self->list_source) { + $text .= q!length) { + $text .= q! SIZE="! . $self->length . q!"!; + } + $text .= '>'; + } + $text .= q!! . "\n"; + } else { + return ''; + } + } else { + return ''; + } + return $text; +} + +=head1 VERSION + +$Id: part_virtual_field.pm,v 1.1.2.1 2003-08-04 23:47: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 + +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, L + +=cut + +1; + + diff --git a/FS/FS/router_field.pm b/FS/FS/router_field.pm deleted file mode 100755 index eee21ab..0000000 --- a/FS/FS/router_field.pm +++ /dev/null @@ -1,146 +0,0 @@ -package FS::router_field; - -use strict; -use vars qw( @ISA ); -use FS::Record qw( qsearchs ); -use FS::part_router_field; -use FS::router; - - -@ISA = qw( FS::Record ); - -=head1 NAME - -FS::router_field - Object methods for router_field records - -=head1 SYNOPSIS - - use FS::router_field; - - $record = new FS::router_field \%hash; - $record = new FS::router_field { 'column' => 'value' }; - - $error = $record->insert; - - $error = $new_record->replace($old_record); - - $error = $record->delete; - - $error = $record->check; - -=head1 DESCRIPTION - -FS::router_field contains values of router xfields. See FS::part_sb_field -for details on the xfield mechanism. - -=over 4 - -=item routerfieldpart - Type of router_field as defined by -FS::part_router_field - -=item routernum - The FS::router to which this value belongs. - -=item value - The contents of the field. - -=back - -=head1 METHODS - - -=over 4 - -=item new HASHREF - -Create a new record. To add the record to the database, see "insert". - -=cut - -sub table { 'router_field'; } - -=item insert - -Adds this record to the database. If there is an error, returns the error, -otherwise returns false. - -=item delete - -Deletes this record from the database. If there is an error, returns the -error, otherwise returns false. - -=item replace OLD_RECORD - -Replaces OLD_RECORD with this one in the database. If there is an error, -returns the error, otherwise returns false. - -=item check - -Checks all fields to make sure this is a valid record. If there is an error, -returns the error, otherwise returns false. Called by the insert and replace -methods. - -=cut - -sub check { - my $self = shift; - - return "routernum must be defined" unless $self->routernum; - return "routerfieldpart must be defined" unless $self->routerfieldpart; - - my $part_router_field = $self->part_router_field; - $_ = $self->value; - - my $check_block = $part_router_field->check_block; - if ($check_block) { - $@ = ''; - my $error = (eval($check_block) or $@); - return $error if $error; - $self->setfield('value' => $_); - } - - ''; #no error -} - -=item part_router_field - -Returns a reference to the FS:part_router_field that defines this -FS::router_field - -=cut - -sub part_router_field { - my $self = shift; - - return qsearchs('part_router_field', - { routerfieldpart => $self->routerfieldpart }); -} - -=item router - -Returns a reference to the FS::router to which this FS::router_field -belongs. - -=cut - -sub router { - my $self = shift; - - return qsearchs('router', { routernum => $self->routernum }); -} - -=back - -=head1 VERSION - -$Id: - -=head1 BUGS - -=head1 SEE ALSO - -FS::svc_broadband, FS::router, FS::router_block, FS::router_field, -schema.html from the base documentation. - -=cut - -1; - diff --git a/FS/FS/sb_field.pm b/FS/FS/sb_field.pm deleted file mode 100755 index d4eb378..0000000 --- a/FS/FS/sb_field.pm +++ /dev/null @@ -1,148 +0,0 @@ -package FS::sb_field; - -use strict; -use vars qw( @ISA ); -use FS::Record qw( qsearchs ); -use FS::part_sb_field; - -use UNIVERSAL qw( can ); - -@ISA = qw( FS::Record ); - -=head1 NAME - -FS::sb_field - Object methods for sb_field records - -=head1 SYNOPSIS - - use FS::sb_field; - - $record = new FS::sb_field \%hash; - $record = new FS::sb_field { 'column' => 'value' }; - - $error = $record->insert; - - $error = $new_record->replace($old_record); - - $error = $record->delete; - - $error = $record->check; - -=head1 DESCRIPTION - -See L for details on this table's mission in life. -FS::sb_field contains the actual values of the xfields defined in -part_sb_field. - -The following fields are supported: - -=over 4 - -=item sbfieldpart - Type of sb_field as defined by FS::part_sb_field - -=item svcnum - The svc_broadband to which this value belongs. - -=item value - The contents of the field. - -=back - -=head1 METHODS - -=over 4 - -=item new HASHREF - -Create a new record. To add the record to the database, see L<"insert">. - -=cut - -sub table { 'sb_field'; } - -=item insert - -Adds this record to the database. If there is an error, returns the error, -otherwise returns false. - -=item delete - -Deletes this record from the database. If there is an error, returns the -error, otherwise returns false. - -=item replace OLD_RECORD - -Replaces OLD_RECORD with this one in the database. If there is an error, -returns the error, otherwise returns false. - -=item check - -Checks the value against the check_block of the corresponding part_sb_field. -Returns whatever the check_block returned (unless the check_block dies, in -which case check returns the die message). Therefore, if the check_block -wants to allow the value to be stored, it must return false. See -L for details. - -=cut - -sub check { - my $self = shift; - - return "svcnum must be defined" unless $self->svcnum; - return "sbfieldpart must be defined" unless $self->sbfieldpart; - - my $part_sb_field = $self->part_sb_field; - - $_ = $self->value; - - my $check_block = $self->part_sb_field->check_block; - if ($check_block) { - $@ = ''; - my $error = (eval($check_block) or $@); # treat fatal errors as errors - return $error if $error; - $self->setfield('value' => $_); - } - - ''; #no error -} - -=item part_sb_field - -Returns a reference to the FS::part_sb_field that defines this FS::sb_field. - -=cut - -sub part_sb_field { - my $self = shift; - - return qsearchs('part_sb_field', { sbfieldpart => $self->sbfieldpart }); -} - -=back - -=item svc_broadband - -Returns a reference to the FS::svc_broadband to which this value is attached. -Nobody's ever going to use this function, but here it is anyway. - -=cut - -sub svc_broadband { - my $self = shift; - - return qsearchs('svc_broadband', { svcnum => $self->svcnum }); -} - -=head1 VERSION - -$Id: - -=head1 BUGS - -=head1 SEE ALSO - -L, schema.html -from the base documentation. - -=cut - -1; - diff --git a/FS/MANIFEST b/FS/MANIFEST index 272b5b7..2f80a33 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 a5cfc16..2cb555e 100755 --- a/FS/bin/freeside-setup +++ b/FS/bin/freeside-setup @@ -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', '', '', diff --git a/htetc/global.asa b/htetc/global.asa index 5553642..796136b 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 481d5a2..c81db86 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 8840b08..def579f 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_sb_field.cgi b/httemplate/browse/part_sb_field.cgi deleted file mode 100644 index 4c9641e..0000000 --- a/httemplate/browse/part_sb_field.cgi +++ /dev/null @@ -1,31 +0,0 @@ -<%= header('svc_broadband extended fields', menubar('Main Menu' => $p)) %> -<% - -my @psf = qsearch('part_sb_field', {}); -my $block; -my $p2 = popurl(2); - -%> - -<% if ($cgi->param('error')) { %> - Error: <%=$cgi->param('error')%> -

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

- -<%=table()%> -Field nameService type -<% foreach $psf (sort {$a->name cmp $b->name} @psf) { %> - - - - - <%=$psf->name%> - <%=$psf->part_svc->svc%> - -<% } %> - - - - diff --git a/httemplate/browse/part_virtual_field.cgi b/httemplate/browse/part_virtual_field.cgi new file mode 100644 index 0000000..9bb5c13 --- /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')) { %> + Error: <%=$cgi->param('error')%> +

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

+ +<% foreach $dbtable (sort { $a cmp $b } keys (%pvfs)) { %> +

<%=$dbtable%>

+ +<%=table()%> +Field nameDescription +<% foreach $pvf (sort {$a->name cmp $b->name} @{ $pvfs{$dbtable} }) { %> + + + + + <%=$pvf->name%> + <%=$pvf->label%> + +<% } %> + +<% } %> + + + diff --git a/httemplate/docs/upgrade10.html b/httemplate/docs/upgrade10.html index 1035510..4c2c17b 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_router_field.cgi b/httemplate/edit/part_router_field.cgi deleted file mode 100644 index 02962b1..0000000 --- a/httemplate/edit/part_router_field.cgi +++ /dev/null @@ -1,71 +0,0 @@ - -<% -my ($routerfieldpart, $part_router_field); - -if ( $cgi->param('error') ) { - $part_router_field = new FS::part_router_field ( { - map { $_, scalar($cgi->param($_)) } fields('part_router_field')}); - $routerfieldpart = $part_router_field->routerfieldpart; -} else { - my($query) = $cgi->keywords; - if ( $query =~ /^(\d+)$/ ) { #editing - $routerfieldpart=$1; - $part_router_field=qsearchs('part_router_field', - {'routerfieldpart' => $routerfieldpart}) - or die "Unknown routerfieldpart!"; - - } else { #adding - $part_router_field = new FS::part_router_field({}); - } -} -my $action = $part_router_field->routerfieldpart ? 'Edit' : 'Add'; - -my $p1 = popurl(1); -print header("$action Router Extended Field Definition", - menubar('Main Menu' => $p, - 'View all Extended Fields' => $p. 'browse/generic.cgi?part_router_field') - ); - -print qq!Error: !, $cgi->param('error'), - "" - if $cgi->param('error'); -%> -
- - - -Field #<%=$routerfieldpart or "(NEW)"%>

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

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

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

-If you don't understand what check_block and -list_source mean, LEAVE THEM BLANK. We mean it. - - - - diff --git a/httemplate/edit/part_virtual_field.cgi b/httemplate/edit/part_virtual_field.cgi new file mode 100644 index 0000000..8bc8438 --- /dev/null +++ b/httemplate/edit/part_virtual_field.cgi @@ -0,0 +1,92 @@ + +<% +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!Error: !, $cgi->param('error'), + "" + if $cgi->param('error'); +%> +
+ + + + +Field #<%=$vfieldpart or "(NEW)"%>

+ +<%=ntable("#cccccc",2)%> + + Name + + + + Table + <% if ($action eq 'Add') { %> + <% + } else { # Edit + %><%=$part_virtual_field->dbtable%> + + <% } %> + + + Label + + + + Length + + + + Check + + + + List source + + +
+ +
+ +

+If you don't understand what check_block and +list_source mean, LEAVE THEM BLANK. We mean it. + + + + diff --git a/httemplate/edit/process/router.cgi b/httemplate/edit/process/router.cgi index 1b7fc38..7e40c48 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 ab8b9f9..4912a3a 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 de0fa6d..5c365a2 100755 --- a/httemplate/edit/router.cgi +++ b/httemplate/edit/router.cgi @@ -26,7 +26,7 @@ if($cgi->param('error')) { %> Error: <%=$cgi->param('error')%> <% } %> -
+ @@ -59,7 +59,7 @@ foreach my $part_svc ( qsearch('part_svc', { svcdb => 'svc_broadband',
$part_svc->svcpart, - routernum => $routernum } ) ? 'CHECKED' : ''%> VALUE="ON"> + routernum => $routernum } ) ? ' CHECKED' : ''%> VALUE="ON"> <%=$part_svc->svcpart%>: <%=$part_svc->svc%> <% } %> diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi index 83cea05..4e01db3 100755 --- a/httemplate/view/svc_acct.cgi +++ b/httemplate/view/svc_acct.cgi @@ -206,11 +206,6 @@ foreach (sort { $a cmp $b } $svc_acct->virtual_fields) { %> <% -foreach (sort { $a cmp $b } keys(%{$sb_field})) { - print q!! . $_ . - q!! . $sb_field->{$_} . - q!!; -} print '

'; diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi index dd5997d..677a4b0 100644 --- a/httemplate/view/svc_broadband.cgi +++ b/httemplate/view/svc_broadband.cgi @@ -69,26 +69,7 @@ foreach (sort { $a cmp $b } $svc_broadband->virtual_fields) { $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!! . $part_sb_field->name . -# q!! . $sb_field->value . -# q!!; -# } -# print ''; - - - my $sb_field = $svc_broadband->sb_field_hashref; - foreach (sort { $a cmp $b } keys(%{$sb_field})) { - print q!! . $_ . - q!! . $sb_field->{$_} . - q!!; - } - print ''; - +print ''; print '
'. joblisting({'svcnum'=>$svcnum}, 1). '' -- cgit v1.1