summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorivan <ivan>2008-04-15 13:42:40 +0000
committerivan <ivan>2008-04-15 13:42:40 +0000
commit477015901c379f8cd6ba46cfaa4a9ae284ae582d (patch)
treef4307128f80570f1f9f38250fb1769e942ec4b99
parent22a294936609cfcb452742a158fafaa55d3a6fd1 (diff)
new package editor
-rw-r--r--FS/FS/Schema.pm12
-rw-r--r--FS/FS/cust_event.pm1
-rw-r--r--FS/FS/cust_main.pm29
-rw-r--r--FS/FS/cust_pkg.pm22
-rw-r--r--FS/FS/option_Common.pm4
-rw-r--r--FS/FS/part_event_option.pm1
-rw-r--r--FS/FS/part_pkg.pm188
-rw-r--r--FS/FS/svc_Common.pm20
-rw-r--r--FS/FS/svc_acct.pm14
-rw-r--r--FS/FS/svc_domain.pm8
-rw-r--r--httemplate/edit/elements/edit.html219
-rw-r--r--httemplate/edit/part_event.html16
-rwxr-xr-xhttemplate/edit/part_pkg.cgi860
-rw-r--r--httemplate/edit/process/elements/process.html64
-rwxr-xr-xhttemplate/edit/process/part_pkg.cgi218
-rw-r--r--httemplate/elements/select-agent_types.html30
-rw-r--r--httemplate/elements/select-taxproduct.html3
-rw-r--r--httemplate/elements/selectlayers.html8
-rw-r--r--httemplate/elements/tr-input-text.html8
-rw-r--r--httemplate/elements/tr-part_pkg_freq.html19
-rw-r--r--httemplate/elements/tr-pkg_svc.html93
-rw-r--r--httemplate/elements/tr-select-agent_types.html19
-rw-r--r--httemplate/elements/tr-title.html12
23 files changed, 1060 insertions, 808 deletions
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index d4a51a6..2666c53 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1007,6 +1007,18 @@ sub tables_hashref {
'index' => [ [ 'promo_code' ], [ 'disabled' ], [ 'agentnum' ], ],
},
+ 'part_pkg_link' => {
+ 'columns' => [
+ 'pkglinknum', 'serial', '', '', '', '',
+ 'src_pkgpart', 'int', '', '', '', '',
+ 'dst_pkgpart', 'int', '', '', '', '',
+ 'link_type', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'pkglinknum',
+ 'unique' => [ [ 'src_pkgpart', 'dst_pkgpart', 'link_type' ] ],
+ 'index' => [ [ 'src_pkgpart' ] ],
+ },
+
'part_pkg_taxclass' => {
'columns' => [
'taxclassnum', 'serial', '', '', '', '',
diff --git a/FS/FS/cust_event.pm b/FS/FS/cust_event.pm
index 5924851..5ca8167 100644
--- a/FS/FS/cust_event.pm
+++ b/FS/FS/cust_event.pm
@@ -344,6 +344,7 @@ sub process_re_X {
}
+#this needs some updating based on the 1.7 cust_bill_event.pm still, i think
sub re_X {
my($method, $beginning, $ending, $failed, $job) = @_;
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index ceefeaf..3490e46 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -8,6 +8,7 @@ use vars qw( $realtime_bop_decline_quiet ); #ugh
use Safe;
use Carp;
use Exporter;
+use Scalar::Util qw( blessed );
use Time::Local qw(timelocal_nocheck);
use Data::Dumper;
use Tie::IxHash;
@@ -1052,7 +1053,7 @@ sub delete {
}
-=item replace OLD_RECORD [ INVOICING_LIST_ARYREF ]
+=item replace [ OLD_RECORD ] [ INVOICING_LIST_ARYREF ]
Replaces the OLD_RECORD with this one in the database. If there is an error,
returns the error, otherwise returns false.
@@ -1068,23 +1069,16 @@ check_invoicing_list first. Here's an example:
sub replace {
my $self = shift;
- my $old = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $self->replace_old;
+
my @param = @_;
+
warn "$me replace called\n"
if $DEBUG;
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- # We absolutely have to have an old vs. new record to make this work.
- if (!defined($old)) {
- $old = qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
- }
-
my $curuser = $FS::CurrentUser::CurrentUser;
if ( $self->payby eq 'COMP'
&& $self->payby ne $old->payby
@@ -1099,6 +1093,13 @@ sub replace {
&& $self->payby =~ /^(CARD|DCRD)$/
&& ( $old->payinfo eq $self->payinfo || $old->paymask eq $self->paymask );
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
my $oldAutoCommit = $FS::UID::AutoCommit;
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
index d413596..09808a4 100644
--- a/FS/FS/cust_pkg.pm
+++ b/FS/FS/cust_pkg.pm
@@ -2,6 +2,7 @@ package FS::cust_pkg;
use strict;
use vars qw(@ISA $disable_agentcheck $DEBUG);
+use Scalar::Util qw( blessed );
use List::Util qw(max);
use Tie::IxHash;
use FS::UID qw( getotaker dbh );
@@ -301,12 +302,17 @@ Calls
=cut
sub replace {
- my( $new, $old, %options ) = @_;
+ my $new = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
+
+ my $options =
+ ( ref($_[0]) eq 'HASH' )
+ ? shift
+ : { @_ };
- # We absolutely have to have an old vs. new record to make this work.
- if (!defined($old)) {
- $old = qsearchs( 'cust_pkg', { 'pkgnum' => $new->pkgnum } );
- }
#return "Can't (yet?) change pkgpart!" if $old->pkgpart != $new->pkgpart;
return "Can't change otaker!" if $old->otaker ne $new->otaker;
@@ -331,8 +337,8 @@ sub replace {
my $dbh = dbh;
foreach my $method ( qw(adjourn expire) ) { # How many reasons?
- if ($options{'reason'} && $new->$method && $old->$method ne $new->$method) {
- my $error = $new->insert_reason( 'reason' => $options{'reason'},
+ if ($options->{'reason'} && $new->$method && $old->$method ne $new->$method) {
+ my $error = $new->insert_reason( 'reason' => $options->{'reason'},
'date' => $new->$method,
);
if ( $error ) {
@@ -357,7 +363,7 @@ sub replace {
}
my $error = $new->SUPER::replace($old,
- $options{options} ? ${options{options}} : ()
+ $options->{options} ? $options->{options} : ()
);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
diff --git a/FS/FS/option_Common.pm b/FS/FS/option_Common.pm
index 441e798..a786ae3 100644
--- a/FS/FS/option_Common.pm
+++ b/FS/FS/option_Common.pm
@@ -161,8 +161,8 @@ sub delete {
Replaces the OLD_RECORD with this one in the database. If there is an error,
returns the error, otherwise returns false.
-If a list hash reference of options is supplied, option records are created or
-modified.
+If a list or hash reference of options is supplied, option records are created
+or modified.
=cut
diff --git a/FS/FS/part_event_option.pm b/FS/FS/part_event_option.pm
index 43e1da9..09b7756 100644
--- a/FS/FS/part_event_option.pm
+++ b/FS/FS/part_event_option.pm
@@ -2,6 +2,7 @@ package FS::part_event_option;
use strict;
use vars qw( @ISA );
+use Scalar::Util qw( blessed );
use FS::UID qw( dbh );
use FS::Record qw( qsearch qsearchs );
use FS::part_event;
diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm
index 1e16f29..cffdc88 100644
--- a/FS/FS/part_pkg.pm
+++ b/FS/FS/part_pkg.pm
@@ -3,6 +3,7 @@ package FS::part_pkg;
use strict;
use vars qw( @ISA %plans $DEBUG );
use Carp qw(carp cluck confess);
+use Scalar::Util qw( blessed );
use Tie::IxHash;
use FS::Conf;
use FS::Record qw( qsearch qsearchs dbh dbdef );
@@ -16,11 +17,9 @@ use FS::pkg_class;
use FS::agent;
use FS::part_pkg_taxoverride;
use FS::part_pkg_taxproduct;
+#XXX#use FS::part_pkg_link;
-@ISA = qw( FS::m2m_Common FS::Record ); # FS::option_Common ); # this can use option_Common
- # when all the plandata bs is
- # gone
-
+@ISA = qw( FS::m2m_Common FS::option_Common );
$DEBUG = 0;
=head1 NAME
@@ -168,61 +167,13 @@ sub insert {
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
- warn " saving legacy plandata" if $DEBUG;
- my $plandata = $self->get('plandata');
- $self->set('plandata', '');
-
warn " inserting part_pkg record" if $DEBUG;
- my $error = $self->SUPER::insert;
+ my $error = $self->SUPER::insert( $options{options} );
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
- if ( $plandata ) {
-
- warn " inserting part_pkg_option records for plandata" if $DEBUG;
- foreach my $part_pkg_option (
- map { /^(\w+)=(.*)$/ or do { $dbh->rollback if $oldAutoCommit;
- return "illegal plandata: $plandata";
- };
- new FS::part_pkg_option {
- 'pkgpart' => $self->pkgpart,
- 'optionname' => $1,
- 'optionvalue' => $2,
- };
- }
- split("\n", $plandata)
- ) {
- my $error = $part_pkg_option->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- } elsif ( $options{'options'} ) {
-
- warn " inserting part_pkg_option records for options hashref" if $DEBUG;
- foreach my $optionname ( keys %{$options{'options'}} ) {
-
- my $part_pkg_option =
- new FS::part_pkg_option {
- 'pkgpart' => $self->pkgpart,
- 'optionname' => $optionname,
- 'optionvalue' => $options{'options'}->{$optionname},
- };
-
- my $error = $part_pkg_option->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- }
-
- }
-
my $conf = new FS::Conf;
if ( $conf->exists('agent_defaultpkg') ) {
warn " agent_defaultpkg set; allowing all agents to purchase package"
@@ -314,16 +265,19 @@ FS::pkg_svc record will be updated.
=cut
sub replace {
- my( $new, $old ) = ( shift, shift );
- my %options = @_;
+ my $new = shift;
- # We absolutely have to have an old vs. new record to make this work.
- if (!defined($old)) {
- $old = qsearchs( 'part_pkg', { 'pkgpart' => $new->pkgpart } );
- }
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
- warn "FS::part_pkg::replace called on $new to replace $old ".
- "with options %options"
+ my $options =
+ ( ref($_[0]) eq 'HASH' )
+ ? shift
+ : { @_ };
+
+ warn "FS::part_pkg::replace called on $new to replace $old with options".
+ join(', ', map "$_ => ". $options->{$_}, keys %$options)
if $DEBUG;
local $SIG{HUP} = 'IGNORE';
@@ -337,6 +291,8 @@ sub replace {
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
+ #plandata shit stays in replace for upgrades until after 2.0 (or edit
+ #_upgrade_data)
warn " saving legacy plandata" if $DEBUG;
my $plandata = $new->get('plandata');
$new->set('plandata', '');
@@ -351,13 +307,13 @@ sub replace {
}
warn " replacing part_pkg record" if $DEBUG;
- my $error = $new->SUPER::replace($old);
+ my $error = $new->SUPER::replace($old, $options->{options} );
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
- warn " inserting part_pkg_option records for plandata" if $DEBUG;
+ warn " inserting part_pkg_option records for plandata: $plandata|" if $DEBUG;
foreach my $part_pkg_option (
map { /^(\w+)=(.*)$/ or do { $dbh->rollback if $oldAutoCommit;
return "illegal plandata: $plandata";
@@ -378,10 +334,10 @@ sub replace {
}
warn " replacing pkg_svc records" if $DEBUG;
- my $pkg_svc = $options{'pkg_svc'} || {};
+ my $pkg_svc = $options->{'pkg_svc'} || {};
foreach my $part_svc ( qsearch('part_svc', {} ) ) {
my $quantity = $pkg_svc->{$part_svc->svcpart} || 0;
- my $primary_svc = $options{'primary_svc'} == $part_svc->svcpart ? 'Y' : '';
+ my $primary_svc = $options->{'primary_svc'} == $part_svc->svcpart ? 'Y' : '';
my $old_pkg_svc = qsearchs('pkg_svc', {
'pkgpart' => $old->pkgpart,
@@ -429,7 +385,8 @@ sub check {
for (qw(setup recur plandata)) {
#$self->set($_=>0) if $self->get($_) =~ /^\s*$/; }
- return "Use of $_ field is deprecated; set a plan and options"
+ return "Use of $_ field is deprecated; set a plan and options: ".
+ $self->get($_)
if length($self->get($_));
$self->set($_, '');
}
@@ -476,6 +433,22 @@ sub check {
'';
}
+=item pkg_comment
+
+Returns an (internal) string representing this package. Currently,
+"pkgpart: pkg - comment", is returned. "pkg - comment" may be returned in the
+future, omitting pkgpart.
+
+=cut
+
+sub pkg_comment {
+ my $self = shift;
+
+ #$self->pkg. ' - '. $self->comment;
+ #$self->pkg. ' ('. $self->comment. ')';
+ $self->pkgpart. ': '. $self->pkg. ' - '. $self->comment;
+}
+
=item pkg_class
Returns the package class, as an FS::pkg_class object, or the empty string
@@ -728,9 +701,20 @@ sub option {
'';
}
+=item dst_pkgpart
+
+=cut
+
+sub part_pkg_link {
+ ();
+ #XXX
+ #my $self = shift;
+ #qsearch('part_pkg_link', { 'src_pkgpart' => $self->pkgpart } );
+}
+
=item part_pkg_taxoverride
-Returns all options as FS::part_pkg_taxoverride objects (see
+Returns all associated FS::part_pkg_taxoverride objects (see
L<FS::part_pkg_taxoverride>).
=cut
@@ -799,7 +783,7 @@ sub _rebless {
my $self = shift;
my $plan = $self->plan;
unless ( $plan ) {
- confess "no price plan found for pkgpart ". $self->pkgpart. "\n"
+ cluck "no price plan found for pkgpart ". $self->pkgpart. "\n"
if $DEBUG;
return $self;
}
@@ -849,6 +833,74 @@ sub calc_cancel { 0; }
=back
+=cut
+
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+
+sub _upgrade_data { # class method
+ my($class, %opts) = @_;
+
+ warn "[FS::part_pkg] upgrading $class\n" if $DEBUG;
+
+ my @part_pkg = qsearch({
+ 'table' => 'part_pkg',
+ 'extra_sql' => "WHERE ". join(' OR ',
+ ( map "($_ IS NOT NULL AND $_ != '' )",
+ qw( plandata setup recur ) ),
+ 'plan IS NULL', "plan = '' ",
+ ),
+ });
+
+ foreach my $part_pkg (@part_pkg) {
+
+ unless ( $part_pkg->plan ) {
+
+ $part_pkg->plan('flat');
+
+ if ( $part_pkg->setup =~ /^\s*([\d\.]+)\s*$/ ) {
+
+ my $opt = new FS::part_pkg_option {
+ 'pkgpart' => $part_pkg->pkgpart,
+ 'optionname' => 'setup_fee',
+ 'optionvalue' => $1,
+ };
+ my $error = $opt->insert;
+ die $error if $error;
+
+ $part_pkg->setup('');
+
+ } else {
+ die "Can't parse part_pkg.setup for fee; convert pkgnum ".
+ $part_pkg->pkgnum. " manually: ". $part_pkg->setup. "\n";
+ }
+
+ if ( $part_pkg->recur =~ /^\s*([\d\.]+)\s*$/ ) {
+
+ my $opt = new FS::part_pkg_option {
+ 'pkgpart' => $part_pkg->pkgpart,
+ 'optionname' => 'recur_fee',
+ 'optionvalue' => $1,
+ };
+ my $error = $opt->insert;
+ die $error if $error;
+
+ $part_pkg->recur('');
+
+ } else {
+ die "Can't parse part_pkg.setup for fee; convert pkgnum ".
+ $part_pkg->pkgnum. " manually: ". $part_pkg->setup. "\n";
+ }
+
+ }
+
+ $part_pkg->replace; #this should take care of plandata, right?
+
+ }
+
+}
+
=head1 SUBROUTINES
=over 4
diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm
index d830f2f..31e53db 100644
--- a/FS/FS/svc_Common.pm
+++ b/FS/FS/svc_Common.pm
@@ -3,6 +3,7 @@ package FS::svc_Common;
use strict;
use vars qw( @ISA $noexport_hack $DEBUG $me );
use Carp qw( cluck carp croak ); #specify cluck have to specify them all..
+use Scalar::Util qw( blessed );
use FS::Record qw( qsearch qsearchs fields dbh );
use FS::cust_main_Mixin;
use FS::cust_svc;
@@ -359,7 +360,7 @@ sub delete {
'';
}
-=item replace OLD_RECORD
+=item replace [ OLD_RECORD ] [ HASHREF | OPTION => VALUE ]
Replaces OLD_RECORD with this one. If there is an error, returns the error,
otherwise returns false.
@@ -367,8 +368,16 @@ otherwise returns false.
=cut
sub replace {
- my ($new, $old) = (shift, shift);
- my %options = @_;
+ my $new = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
+
+ my $options =
+ ( ref($_[0]) eq 'HASH' )
+ ? shift
+ : { @_ };
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
@@ -381,9 +390,6 @@ sub replace {
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
- # We absolutely have to have an old vs. new record to make this work.
- $old = $new->replace_old unless defined($old);
-
my $error = $new->set_auto_inventory;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
@@ -399,7 +405,7 @@ sub replace {
#new-style exports!
unless ( $noexport_hack ) {
- my $export_args = $options{'export_args'} || [];
+ my $export_args = $options->{'export_args'} || [];
#not quite false laziness, but same pattern as FS::svc_acct::replace and
#FS::part_export::sqlradius::_export_replace. List::Compare or something
diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm
index 4343df5..3e3ecb5 100644
--- a/FS/FS/svc_acct.pm
+++ b/FS/FS/svc_acct.pm
@@ -14,6 +14,7 @@ use vars qw( @ISA $DEBUG $me $conf $skip_fuzzyfiles
$radius_password $radius_ip
$dirhash
@saltset @pw_set );
+use Scalar::Util qw( blessed );
use Carp;
use Fcntl qw(:flock);
use Date::Format;
@@ -734,14 +735,15 @@ contain an arrayref of group names. See L<FS::radius_usergroup>.
=cut
sub replace {
- my ( $new, $old ) = ( shift, shift );
- my $error;
+ my $new = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
+
warn "$me replacing $old with $new\n" if $DEBUG;
- # We absolutely have to have an old vs. new record to make this work.
- if (!defined($old)) {
- $old = qsearchs( 'svc_acct', { 'svcnum' => $new->svcnum } );
- }
+ my $error;
return "can't modify system account" if $old->_check_system;
diff --git a/FS/FS/svc_domain.pm b/FS/FS/svc_domain.pm
index 758b399b..47aa8f3 100644
--- a/FS/FS/svc_domain.pm
+++ b/FS/FS/svc_domain.pm
@@ -6,6 +6,7 @@ use vars qw( @ISA $whois_hack $conf
$soarefresh $soaretry
);
use Carp;
+use Scalar::Util qw( blessed );
use Date::Format;
#use Net::Whois::Raw;
use Net::Domain::TLD qw(tld_exists);
@@ -289,10 +290,11 @@ returns the error, otherwise returns false.
=cut
sub replace {
- my ( $new, $old ) = ( shift, shift );
+ my $new = shift;
- # We absolutely have to have an old vs. new record to make this work.
- $old = $new->replace_old unless defined($old);
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
return "Can't change domain - reorder."
if $old->getfield('domain') ne $new->getfield('domain');
diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html
index c80586a..150ed85 100644
--- a/httemplate/edit/elements/edit.html
+++ b/httemplate/edit/elements/edit.html
@@ -22,9 +22,12 @@ Example:
#percentage
#checkbox
#select
- #selectlayers (can't use after a tablebreak-tr-title yet... grep "OneTrueTable")
+ #selectlayers (can now use after a tablebreak-tr-title... but not inside columnstart/columnnext/columnend)
#title
#tablebreak-tr-title
+ #columnstart
+ #columnnext
+ #columnend
#hidden - hidden value from object
#fixed - display fixed value from object or here
#fixed-country
@@ -32,29 +35,32 @@ Example:
'value' => 'Y', #for checkbox, title, fixed, fixedhidden
'disabled' => 0,
'onchange' => 'javascript_function',
- 'm2name_table' => 'table_name', #only tested w/
- # selectlayers so far
- # might work w/select
- # dunno others
- 'm2name_namecol' => 'name_column', #
- 'm2name_label' => 'Label', #
- 'm2name_new_default' => \@table_name_objects, #default
- #m2name
- #objects for
- #new records
- 'm2name_error_callback' => sub { my($cgi, $object) = @_; },
- 'm2name_remove_warnings' => \%warnings, #hashref of warning
- #messages for
- #m2name removal
- 'm2name_new_js' => 'function_name', #javascript function
- #called on spawned rows
- #(one arg: new_element)
- 'm2name_remove_js' => 'function_name', #js function called
- #when a row is
- #deleted
- #(three args:
- # value, text,
- # 'no_match')
+
+ #m2 stuff only tested w/selectlayers so far
+ #might work w/select too, dunno others
+ 'm2name_table' => 'table_name',
+ 'm2name_namecol' => 'name_column',
+ #OR#
+ 'm2m_table' =>
+ 'm2m_target_table' =>
+ 'm2m_srccol' => #opt, if not the same as this table
+ 'm2m_dstcol' => #opt, if not the same as target table
+
+ 'm2_label' => 'Label', #
+ 'm2_new_default' => \@table_name_objects, #default
+ #m2 objects for
+ #new records
+ 'm2_error_callback' => sub { my($cgi, $object) = @_; },
+ 'm2_remove_warnings' => \%warnings, #hashref of warning
+ #messages for m2
+ #removal
+ 'm2_new_js' => 'function_name', #javascript function called
+ #on spawned rows (one arg:
+ #new_element)
+ 'm2_remove_js' => 'function_name', #js function called when
+ #a row is deleted (three
+ #args: value, text,
+ #'no_match')
#layer_fields & layer_values_callback only for selectlayer
'layer_fields' => [
'fieldname' => 'Label',
@@ -93,6 +99,9 @@ Example:
# returns a hashref for the new object
'new_hashref_callback'
+
+ # returns the new object iself (otherwise, ->new is called)
+ 'new_object_callback'
#run when adding
'new_callback' => sub { my( $cgi, $object, $fields_listref ) = @_; },
@@ -103,18 +112,24 @@ Example:
#XXX describe
'field_callback' => sub { },
+ 'viewall_dir' => '', #'search' or 'browse', defaults to 'search'
+
+ 'html_init' => '', #after the header/menubar
+
#string or coderef of additional HTML to add before </TABLE>
'html_table_bottom' => '',
-
- 'viewall_dir' => '', #'search' or 'browse', defaults to 'search'
-
+
+ #after </TABLE> but before the submit
'html_bottom' => '', #string
'html_bottom' => sub {
my $object = shift;
# ...
"html_string";
},
-
+
+ #at the very bottom (well, as low as you can go from here)
+ 'html_foot' => '',
+
# overrides default popurl(1)."process/$table.html"
'post_url' => popurl(1).'process/something',
@@ -131,6 +146,14 @@ Example:
)
%>
+<% defined($opt{'html_init'})
+ ? ( ref($opt{'html_init'})
+ ? &{$opt{'html_init'}}()
+ : $opt{'html_init'}
+ )
+ : ''
+%>
+
<% include('/elements/error.html') %>
% my $url = $opt{'post_url'} || popurl(1)."process/$table.html";
@@ -148,10 +171,11 @@ Example:
</B></FONT>
#<% $object->$pkey() || "(NEW)" %>
-%# <% ntable("#cccccc",0) %>
-<TABLE ID="OneTrueTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+% my $tablenum = 0;
+<TABLE ID="TableNumber<% $tablenum++ %>" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
% my $g_row = 0;
+% my @g_row_stack = ();
% foreach my $f ( map { ref($_) ? $_ : {'field'=>$_} }
% @$fields
% ) {
@@ -169,9 +193,11 @@ Example:
% my $onchange = $f->{'onchange'};
%
% my $layer_values = {};
-% if ( $f->{'layer_values_callback'} && ! $f->{'m2name_table'} ) {
-% $layer_values = &{ $f->{'layer_values_callback'} }( $cgi, $object );
-% }
+% $layer_values = &{ $f->{'layer_values_callback'} }( $cgi, $object )
+% if $f->{'layer_values_callback'}
+% && ! $f->{'m2name_table'}
+% && ! $f->{'m2m_table'};
+%
% warn "layer values: ". Dumper($layer_values)
% if $opt{'debug'};
%
@@ -196,8 +222,19 @@ Example:
% 'layer_fields' => $f->{'layer_fields'},
% 'layer_values' => $layer_values,
% 'html_between' => $f->{'html_between'},
+%
+% #umm.
+% 'disabled' => $f->{'disabled'},
% );
%
+% #select-table
+% $include_common{$_} = $f->{$_}
+% foreach grep exists($f->{$_}), qw( table name_col );
+%
+% if ( $type eq 'tablebreak-tr-title' ) {
+% $include_common{'table_id'} = 'TableNumber'. $tablenum++
+% }
+%
% my $layer_prefix_on = '';
%
% my $include_sub = sub {
@@ -207,7 +244,7 @@ Example:
%
% my $include = $type;
% $include = "input-$include" if $include =~ /^(text|money|percentage)$/;
-% $include = "tr-$include" unless $include =~ /^(hidden|tablebreak)/;
+% $include = "tr-$include" unless $include =~ /^(hidden|tablebreak|column)/;
%
% $include_common{'layer_prefix'} = "$field$fieldnum."
% if $layer_prefix_on;
@@ -223,33 +260,56 @@ Example:
% @include;
% };
%
-% $g_row++;
-% $g_row++ if $type eq 'title';
+% unless ( $type =~ /^column/ ) {
+% $g_row = 1 if $type eq 'tablebreak-tr-title';
+% $g_row++;
+% $g_row++ if $type eq 'title';
+% } else {
+% if ( $type eq 'columnstart' ) {
+% push @g_row_stack, $g_row++;
+% $g_row = 0;
+% #} elsif ( $type eq 'columnnext' ) {
+% } elsif ( $type eq 'columnend' ) {
+% $g_row = pop @g_row_stack;
+% }
+%
+% }
%
% my $fieldnum = '';
% my $curr_value = '';
-% if ( $f->{'m2name_table'} ) { #XXX test this for all types of fields
-% my $table = $f->{'m2name_table'};
-% my $col = $f->{'m2name_namecol'};
+% if ( $f->{'m2name_table'} || $f->{'m2m_table'} ) { #XXX test this for all
+% #types of fields
+% my($table, $col);
+% if ( $f->{'m2name_table'} ) {
+% $table = $f->{'m2name_table'};
+% $col = $f->{'m2name_namecol'};
+% } elsif ( $f->{'m2m_table'} ) {
+% $table = $f->{'m2m_table'}; #XXX method name
+% $col = $f->{'m2m_dstcol'}; #XXX or get it from schema, defualt to
+% #same name as primary key of
+% #m2m_target_table
+% }
% $fieldnum = 0;
% $layer_prefix_on = 1;
-% #print out the fields for the existing m2names
+% #print out the fields for the existing m2s
% my @existing = ();
% if ( $mode eq 'error' ) {
-% @existing = &{ $f->{'m2name_error_callback'} }( $cgi, $object );
+% @existing = &{ $f->{'m2_error_callback'} }( $cgi, $object );
% } elsif ( $object->$pkey() ) { # $mode eq 'edit'
% @existing = $object->$table();
-% } elsif ( $f->{'m2name_new_default'} ) { # && $mode eq 'new'
-% @existing = @{ $f->{'m2name_new_default'} };
+% warn scalar(@existing). " from $object->$table: ". join('/', @existing)
+% if $opt{'debug'};
+% } elsif ( $f->{'m2_new_default'} ) { # && $mode eq 'new'
+% @existing = @{ $f->{'m2_new_default'} };
% }
% foreach my $name_obj ( @existing ) {
%
% my $ex_label = '<INPUT TYPE="button" VALUE="X" TITLE="Remove this '.
-% lc($f->{'m2name_label'}).
+% lc($f->{'m2_label'}).
% qq(" onClick="remove_$field($fieldnum);").
% ' STYLE="color:#ff0000;font-weight:bold;'.
% 'padding-left:2px;padding-right:2px"'.
-% '>&nbsp;'. ($f->{'m2name_label'} || $field ). ' ';
+% '>&nbsp;'. ($f->{'m2_label'} || $field ). ' ';
%
% if ( $f->{'layer_values_callback'} ) {
% my %switches = ( 'mode' => $mode );
@@ -276,10 +336,13 @@ Example:
% #$field .= $fieldnum;
% $onchange .= "\nspawn_$field(what);";
% } else {
-% $curr_value =
-% ($opt{'value_callback'} && $mode ne 'error')
-% ? &{ $opt{'value_callback'} }( $f->{'field'}, $object->$field() )
-% : $object->$field();
+% if ( $f->{curr_value_callback} ) {
+% $curr_value = &{ $f->{curr_value_callback} }( $cgi, $object, $field ),
+% } else {
+% $curr_value = $object->$field();
+% }
+% $curr_value = &{ $opt{'value_callback'} }( $f->{'field'}, $curr_value )
+% if $opt{'value_callback'} && $mode ne 'error';
% }
%
% my @include = &{ $include_sub }(
@@ -287,18 +350,19 @@ Example:
% 'fieldnum' => $fieldnum,
% 'curr_value' => $curr_value,
% 'object' => $object,
+% 'cgi' => $cgi,
% 'onchange' => $onchange,
% 'cell_style' => ( $fieldnum ? 'border-top:1px solid black' : '' ),
% );
<% include( @include ) %>
-% if ( $f->{'m2name_table'} ) {
+% if ( $f->{'m2name_table'} || $f->{'m2m_table'} ) {
<SCRIPT TYPE="text/javascript">
- var rownum = <% $g_row %>;
- var fieldnum = <% $fieldnum %>;
+ var <%$field%>_rownum = <% $g_row %>;
+ var <%$field%>_fieldnum = <% $fieldnum %>;
function spawn_<%$field%>(what) {
@@ -310,23 +374,25 @@ Example:
alert(what.name + " didn't match?!");
return;
}
- if ( match[1] != fieldnum ) {
+ if ( match[1] != <%$field%>_fieldnum ) {
return;
}
// change the label on the last entry & add a remove button
- var prev_label = document.getElementById('<% $field %>_label' + fieldnum );
- prev_label.innerHTML = '<INPUT TYPE="button" VALUE="X" TITLE="Remove this <% lc($f->{'m2name_label'}) %>" onClick="remove_<% $field %>(' + fieldnum + ');" STYLE="color:#ff0000;font-weight:bold;padding-left:2px;padding-right:2px" >&nbsp;<% $f->{'m2name_label'} || $field %>';
+ var prev_label = document.getElementById('<% $field %>_label' + <%$field%>_fieldnum );
+ prev_label.innerHTML = '<INPUT TYPE="button" VALUE="X" TITLE="Remove this <% lc($f->{'m2_label'}) %>" onClick="remove_<% $field %>(' + <%$field%>_fieldnum + ');" STYLE="color:#ff0000;font-weight:bold;padding-left:2px;padding-right:2px" >&nbsp;<% $f->{'m2_label'} || $field %>';
- fieldnum++;
+ <%$field%>_fieldnum++;
//get the new widget
% $include[0] =~ s(^/elements/tr-)(/elements/);
% my @layer_opt = ( @include,
% 'field' => $field."MAGIC_NUMBER",
+% 'id' => $field."MAGIC_NUMBER",
% 'layer_prefix' => $field."MAGIC_NUMBER.",
% );
+% warn @layer_opt if $opt{'debug'};
var newrow = <% include(@layer_opt, html_only=>1) |js_string %>;
@@ -338,8 +404,8 @@ Example:
// substitute in the new field name
var magic_regex = /MAGIC_NUMBER/g;
- newrow = newrow.replace( magic_regex, fieldnum );
- newfunc = newfunc.replace( magic_regex, fieldnum );
+ newrow = newrow.replace( magic_regex, <%$field%>_fieldnum );
+ newfunc = newfunc.replace( magic_regex, <%$field%>_fieldnum );
// evaluate new_func
if (window.ActiveXObject) {
@@ -352,13 +418,13 @@ Example:
// add new row
//hmm, can't use selectlayers after a tablebreak-title for now
- var table = document.getElementById('OneTrueTable');
+ var table = document.getElementById('TableNumber<% $tablenum-1 %>');
- var row = table.insertRow(rownum++);
+ var row = table.insertRow(<%$field%>_rownum++);
var label_cell = document.createElement('TD');
- label_cell.id = '<% $field %>_label' + fieldnum;
+ label_cell.id = '<% $field %>_label' + <%$field%>_fieldnum;
label_cell.style.textAlign = "right";
label_cell.style.verticalAlign = "top";
@@ -378,10 +444,10 @@ Example:
row.appendChild(widget_cell);
-% if ( $f->{'m2name_new_js'} ) {
+% if ( $f->{'m2_new_js'} ) {
// take out items selected in previous dropdowns
- var new_element = document.getElementById("<%$field%>" + fieldnum );
- <% $f->{'m2name_new_js'} %>(new_element);
+ var new_element = document.getElementById("<%$field%>" + <%$field%>_fieldnum );
+ <% $f->{'m2_new_js'} %>(new_element);
if ( new_element.length < 2 ) {
//just the ** Select new **, so don't display the row
@@ -395,7 +461,12 @@ Example:
//alert("remove <%$field%> " + remove_fieldnum);
var select = document.getElementById('<%$field%>' + remove_fieldnum);
-% my $warnings = $f->{'m2name_remove_warnings'};
+ if ( ! select ) {
+ alert("can't find element <%$field%>" + remove_fieldnum);
+ return;
+ }
+
+% my $warnings = $f->{'m2_remove_warnings'};
% if ( $warnings ) {
var sel_value = select.options[select.selectedIndex].value;
% foreach my $value ( keys %$warnings ) {
@@ -411,9 +482,9 @@ Example:
var label_td = document.getElementById('<%$field%>_label' + remove_fieldnum );
label_td.parentNode.style.display = 'none';
-% if ( $f->{m2name_remove_js} ) {
+% if ( $f->{m2_remove_js} ) {
var opt = select.options[select.selectedIndex];
- <% $f->{m2name_remove_js} %>( opt.value, opt.text, 'no_match');
+ <% $f->{m2_remove_js} %>( opt.value, opt.text, 'no_match');
% }
}
@@ -442,6 +513,11 @@ Example:
</FORM>
+<% ref( $opt{'html_foot'} )
+ ? &{ $opt{'html_foot'} }( $object )
+ : $opt{'html_foot'}
+%>
+
<% include("/elements/footer.html") %>
<%init>
@@ -471,7 +547,6 @@ if ( $cgi->param('error') ) {
$mode = 'error';
-
$object = $class->new( {
map { $_ => scalar($cgi->param($_)) } fields($table)
});
@@ -515,16 +590,18 @@ if ( $cgi->param('error') ) {
? &{$opt{'new_hashref_callback'}}
: {};
- $object = $class->new( $hashref );
+ $object = $opt{'new_object_callback'}
+ ? &{$opt{'new_object_callback'}}( $cgi, $hashref, $fields, \%opt )
+ : $class->new( $hashref );
&{$opt{'new_callback'}}($cgi, $object, $fields)
if $opt{'new_callback'};
}
-my $action = $object->$pkey() ? 'Edit' : 'Add';
+$opt{action} ||= $object->$pkey() ? 'Edit' : 'Add';
-my $title = "$action $opt{'name'}";
+my $title = $opt{action}. ' '. $opt{name};
my $viewall_url = $p . ( $opt{'viewall_dir'} || 'search' ) . "/$table.html";
$viewall_url = $opt{'viewall_url'} if $opt{'viewall_url'};
diff --git a/httemplate/edit/part_event.html b/httemplate/edit/part_event.html
index 5b14edf..6a53222 100644
--- a/httemplate/edit/part_event.html
+++ b/httemplate/edit/part_event.html
@@ -35,14 +35,12 @@
html_between => n_a('action'),
m2name_table => 'part_event_condition',
m2name_namecol => 'conditionname',
- m2name_label => 'Condition',
- m2name_new_default => \@implicit_condition_objs,
- m2name_error_callback =>
- $condition_error_callback,
- m2name_remove_warnings =>
- \%condition_remove_warnings,
- m2name_new_js => 'condition_repop',
- m2name_remove_js => 'condition_add',
+ m2_label => 'Condition',
+ m2_new_default => \@implicit_condition_objs,
+ m2_error_callback => $condition_error_callback,
+ m2_remove_warnings => \%condition_remove_warnings,
+ m2_new_js => 'condition_repop',
+ m2_remove_js => 'condition_add',
},
{ type => 'title',
value => 'Event Action',
@@ -639,7 +637,7 @@ my $condition_error_callback = sub {
};
my $condition_layer_values = sub {
- #m2name_table option causes this to be
+ #m2_table option causes this to be
# part_event_condition instead of part_event
my ( $cgi, $part_event_condition, $switches ) = @_;
scalar( #force hashref
diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi
index c00af19..959fb30 100755
--- a/httemplate/edit/part_pkg.cgi
+++ b/httemplate/edit/part_pkg.cgi
@@ -1,437 +1,146 @@
-<% include('/elements/header.html', "$action Package Definition", menubar(
- 'View all packages' => popurl(2). 'browse/part_pkg.cgi',
-)) %>
-% #), ' onLoad="visualize()"');
-
-<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/overlibmws.js"></SCRIPT>
-<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/overlibmws_iframe.js"></SCRIPT>
-<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/overlibmws_draggable.js"></SCRIPT>
-<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/iframecontentmws.js"></SCRIPT>
-
-<% include('/elements/error.html') %>
-
-<FORM NAME="dummy">
-
-<% itable('',8,1) %><TR><TD VALIGN="top">
-
-Package information
-
-<% ntable("#cccccc",2) %>
- <TR>
- <TD ALIGN="right">Package Definition #</TD>
- <TD BGCOLOR="#ffffff">
- <% $hashref->{pkgpart} ? $hashref->{pkgpart} : "(NEW)" %>
- </TD>
- </TR>
- <TR>
- <TD ALIGN="right">Package (customer-visible)</TD>
- <TD>
- <INPUT TYPE="text" NAME="pkg" SIZE=32 VALUE="<% $part_pkg->pkg %>">
- </TD>
- </TR>
- <TR>
- <TD ALIGN="right">Comment (customer-hidden)</TD>
- <TD>
- <INPUT TYPE="text" NAME="comment" SIZE=32 VALUE="<%$part_pkg->comment%>">
- </TD>
- </TR>
- <% include( '/elements/tr-select-pkg_class.html',
- 'curr_value' => $part_pkg->classnum,
- )
- %>
- <TR>
- <TD ALIGN="right">Promotional code</TD>
- <TD>
- <INPUT TYPE="text" NAME="promo_code" SIZE=32 VALUE="<%$part_pkg->promo_code%>">
- </TD>
- </TR>
- <TR>
- <TD ALIGN="right">Disable new orders</TD>
- <TD>
- <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>
- </TD>
- </TR>
-
-</TABLE>
-
-</TD><TD VALIGN="top">
-
-Tax information
-<% ntable("#cccccc", 2) %>
- <TR>
- <TD ALIGN="right">Setup fee tax exempt</TD>
- <TD>
- <INPUT TYPE="checkbox" NAME="setuptax" VALUE="Y" <% $hashref->{setuptax} eq 'Y' ? ' CHECKED' : '' %>>
- </TD>
- </TR>
- <TR>
- <TD ALIGN="right">Recurring fee tax exempt</TD>
- <TD>
- <INPUT TYPE="checkbox" NAME="recurtax" VALUE="Y" <% $hashref->{recurtax} eq 'Y' ? ' CHECKED' : '' %>>
- </TD>
- </TR>
-
-% if ( $conf->exists('enable_taxclasses') ) {
-
- <TR>
- <TD align="right">Tax class</TD>
- <TD>
- <% include('/elements/select-taxclass.html', $hashref->{taxclass} ) %>
- </TD>
- </TR>
-
-% } else {
-
- <% include('/elements/select-taxclass.html', $hashref->{taxclass} ) %>
-
-% }
-
-% if ( $conf->exists('enable_taxproducts') ) {
-
- <TR><TD colspan="2">
- <% ntable("#cccccc", 2) %>
- <TR>
- <TD align="right">Tax product</TD>
- <TD>
- <INPUT name="part_pkg_taxproduct_taxproductnum" id="taxproductnum" type="hidden" value="<% $hashref->{'taxproductnum'}%>">
- <INPUT name="part_pkg_taxproduct_description" id="taxproductnum_description" type="text" value="<% $taxproduct_description %>" size="12" onclick="overlib( OLiframeContent('<% $p %>/browse/part_pkg_taxproduct.cgi?_type=select&id=taxproductnum&taxproductnum='+document.getElementById('taxproductnum').value, 1000, 400, 'tax_product_popup'), CAPTION, 'Select product', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK); return false;">
- </TD>
- </TR>
- <TR>
- <TD colspan="2" align="right">
- <INPUT name="tax_override" id="tax_override" type="hidden" value="<% $tax_override %>">
- <A href="javascript:void(0)" onclick="overlib( OLiframeContent('part_pkg_taxoverride.html?selected='+document.getElementById('tax_override').value, 1100, 600, 'tax_product_popup'), CAPTION, 'Edit product tax overrides', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK); return false;">
- <% $tax_override ? 'Edit tax overrides' : 'Override taxes' %>
- </A>
- </TD>
- </TR>
- </TABLE>
- </TD></TR>
-
-% } else {
-
- <INPUT TYPE="hidden" NAME="taxproductnum" VALUE="<% $hashref->{taxproductnum} %>">
- <INPUT TYPE="hidden" NAME="tax_override" VALUE="<% $tax_override %>">
-
-% }
-
-</TABLE>
-<BR>
-
-Line-item revenue recognition
-<% ntable("#cccccc", 2) %>
-% tie my %weight, 'Tie::IxHash',
-% 'pay_weight' => 'Payment',
-% 'credit_weight' => 'Credit'
-% ;
-% foreach my $weight (keys %weight) {
- <TR>
- <TD ALIGN="right"><% $weight{$weight} %> weight</TD>
- <TD>
- <INPUT TYPE="text" NAME="<% $weight %>" SIZE=6 VALUE=<% $hashref->{$weight} || 0 %>>
- </TD>
- </TR>
-% }
-</TABLE>
-
-</TD><TD VALIGN="top">
-
-% if ( $cgi->param('clone') ) {
-
- <INPUT TYPE="hidden" NAME="agent_type" VALUE="">
-
-% } elsif ( scalar(@all_agent_types) == 1) {
-
- <INPUT TYPE="hidden" NAME="agent_type" VALUE="<% $all_agent_types[0] %>">
-
-% } else {
-
- Reseller information
- <% ntable("#cccccc", 2) %>
- <TR>
- <TD ALIGN="right"><% 'Agent Types' %></TD>
- <TD>
- <% include( '/elements/select-table.html',
- 'element_name' => 'agent_type',
- 'table' => 'agent_type',
- 'name_col' => 'atype',
- 'value' => \@agent_type,
- 'multiple' => '1',
- 'element_etc' => 'size="10"',
- )
- %>
- </TD>
- </TR>
- </TABLE>
-
-% }
-
-</TD></TR></TABLE>
-
-%my $thead = "\n\n". ntable('#cccccc', 2).
-% '<TR><TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Quan.</FONT></TH>'.
-% '<TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Primary</FONT></TH>'.
-% '<TH BGCOLOR="#dcdcdc">Service</TH></TR>';
-
-<BR><BR>Services included
-<% itable('', 4, 1) %><TR><TD VALIGN="top">
-<% $thead %>
-%
-%
-%my $where = "WHERE disabled IS NULL OR disabled = ''";
-%if ( $pkgpart ) {
-% $where .= " OR 0 < ( SELECT quantity FROM pkg_svc
-% WHERE pkg_svc.svcpart = part_svc.svcpart
-% AND pkgpart = $pkgpart
-% )";
-%}
-%my @part_svc = qsearch('part_svc', {}, '', $where);
-%my $q_part_pkg = $clone_part_pkg || $part_pkg;
-%my %pkg_svc = map { $_->svcpart => $_ } $q_part_pkg->pkg_svc;
-%
-%my @fixups = ();
-%my $count = 0;
-%my $columns = 3;
-%foreach my $part_svc ( @part_svc ) {
-% my $svcpart = $part_svc->svcpart;
-% my $pkg_svc = $pkg_svc{$svcpart}
-% || new FS::pkg_svc ( {
-% 'pkgpart' => $pkgpart,
-% 'svcpart' => $svcpart,
-% 'quantity' => 0,
-% 'primary_svc' => '',
-% } );
-% if ( $cgi->param('error') ) {
-% my $primary_svc = ( $pkg_svc->primary_svc =~ /^Y/i );
-% my $pkg_svc_primary = scalar($cgi->param('pkg_svc_primary'));
-% $pkg_svc->primary_svc('')
-% if $primary_svc && $pkg_svc_primary != $svcpart;
-% $pkg_svc->primary_svc('Y')
-% if ! $primary_svc && $pkg_svc_primary == $svcpart;
-% }
-%
-% push @fixups, "pkg_svc$svcpart";
-%
-% my $quan = 0;
-% if ( $cgi->param("pkg_svc$svcpart") =~ /^\s*(\d+)\s*$/ ) {
-% $quan = $1;
-% } elsif ( $pkg_svc->quantity ) {
-% $quan = $pkg_svc->quantity;
-% }
-
-
- <TR>
- <TD>
- <INPUT TYPE="text" NAME="pkg_svc<% $svcpart %>" SIZE=4 MAXLENGTH=3 VALUE="<% $quan %>">
- </TD>
-
- <TD>
- <INPUT TYPE="radio" NAME="pkg_svc_primary" VALUE="<% $svcpart %>" <% $pkg_svc->primary_svc =~ /^Y/i ? ' CHECKED' : '' %>>
- </TD>
-
- <TD>
- <A HREF="part_svc.cgi?<% $part_svc->svcpart %>"><% $part_svc->svc %></A> <% $part_svc->disabled =~ /^Y/i ? ' (DISABLED' : '' %>
- </TD>
- </TR>
-% foreach ( 1 .. $columns-1 ) {
-% if ( $count == int( $_ * scalar(@part_svc) / $columns ) ) {
-%
-
- </TABLE></TD><TD VALIGN="top"><% $thead %>
-% }
-% }
-% $count++;
-%
-% }
-
-
-</TR></TABLE></TD></TR></TABLE>
-% foreach my $f ( qw( clone pkgnum ) ) { #safe, these were untained in %init
- <INPUT TYPE="hidden" NAME="<% $f %>" VALUE="<% $cgi->param($f) %>">
-% }
-
-<INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $part_pkg->pkgpart %>">
-%
-%
-%# prolly should be in database
-%tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
-%
-%my %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); }
-% split("\n", ($clone_part_pkg||$part_pkg)->plandata );
-%#warn join("\n", map { "$_: $plandata{$_}" } keys %plandata ). "\n";
-%
-%tie my %options, 'Tie::IxHash', map { $_=>$plans{$_}->{'name'} } keys %plans;
-%
-%#my @form_select = ('classnum');
-%#if ( $conf->exists('enable_taxclasses') ) {
-%# push @form_select, 'taxclass';
-%#} else {
-%# push @fixups, 'taxclass'; #hidden
-%#}
-%my @form_elements = ( 'classnum', 'taxclass', 'agent_type', 'tax_override' );
-%
-%my @form_radio = ( 'pkg_svc_primary' );
-%
-%tie my %freq, 'Tie::IxHash', %{FS::part_pkg->freqs_href()};
-%if ( $part_pkg->dbdef_table->column('freq')->type =~ /(int)/i ) {
-% delete $freq{$_} foreach grep { ! /^\d+$/ } keys %freq;
-%}
-%
-%#this should be replaced by /elements/selectlayers.html
-%my $widget = new HTML::Widgets::SelectLayers(
-% 'selected_layer' => $part_pkg->plan,
-% 'options' => \%options,
-% 'form_name' => 'dummy',
-% 'form_action' => 'process/part_pkg.cgi',
-% 'form_elements' => \@form_elements,
-% 'form_text' => [ qw(pkg comment promo_code clone pkgnum pkgpart),
-% qw(pay_weight credit_weight), #keys(%weight),
-% qw(taxproductnum),
-% @fixups,
-% ],
-% 'form_checkbox' => [ qw(setuptax recurtax disabled) ],
-% 'form_radio' => \@form_radio,
-% 'layer_callback' => sub {
-% my $layer = shift;
-% my $html = qq!<INPUT TYPE="hidden" NAME="plan" VALUE="$layer">!.
-% ntable("#cccccc",2);
-% $html .= '
-% <TR>
-% <TD ALIGN="right">Recurring fee frequency </TD>
-% <TD><SELECT NAME="freq">
-% ';
-%
-% my @freq = keys %freq;
-% @freq = grep { /^\d+$/ } @freq
-% if exists($plans{$layer}->{'freq'}) && $plans{$layer}->{'freq'} eq 'm';
-% foreach my $freq ( @freq ) {
-% $html .= qq(<OPTION VALUE="$freq");
-% $html .= ' SELECTED' if $freq eq $part_pkg->freq;
-% $html .= ">$freq{$freq}";
-% }
-% $html .= '</SELECT></TD></TR>';
-%
-% my $href = $plans{$layer}->{'fields'};
-% foreach my $field ( exists($plans{$layer}->{'fieldorder'})
-% ? @{$plans{$layer}->{'fieldorder'}}
-% : keys %{ $href }
-% ) {
-%
-% $html .= '<TR><TD ALIGN="right">'. $href->{$field}{'name'}. '</TD><TD>';
-%
-% my $format = sub { shift };
-% $format = $href->{$field}{'format'} if exists($href->{$field}{'format'});
-%
-% if ( ! exists($href->{$field}{'type'}) ) {
-%
-% $html .= qq!<INPUT TYPE="text" NAME="$field" VALUE="!.
-% ( exists($plandata{$field})
-% ? &$format($plandata{$field})
-% : $href->{$field}{'default'} ).
-% qq!" onChange="fchanged(this)">!;
-%
-% } elsif ( $href->{$field}{'type'} eq 'checkbox' ) {
-%
-% $html .= qq!<INPUT TYPE="checkbox" NAME="$field" VALUE=1 !.
-% ( exists($plandata{$field}) && $plandata{$field}
-% ? ' CHECKED'
-% : ''
-% ). '>';
-%
-% } elsif ( $href->{$field}{'type'} =~ /^select/ ) {
-%
-% $html .= '<SELECT';
-% $html .= ' MULTIPLE'
-% if $href->{$field}{'type'} eq 'select_multiple';
-% $html .= qq! NAME="$field" onChange="fchanged(this)">!;
-%
-% if ( $href->{$field}{'select_table'} ) {
-% foreach my $record (
-% qsearch( $href->{$field}{'select_table'},
-% $href->{$field}{'select_hash'} )
-% ) {
-% my $value = $record->getfield($href->{$field}{'select_key'});
-% $html .= qq!<OPTION VALUE="$value"!.
-% ( $plandata{$field} =~ /(^|, *)$value *(,|$)/
-% ? ' SELECTED'
-% : ''
-% ).
-% '>'. $record->getfield($href->{$field}{'select_label'});
-% }
-% } elsif ( $href->{$field}{'select_options'} ) {
-% foreach my $key ( keys %{ $href->{$field}{'select_options'} } ) {
-% my $label = $href->{$field}{'select_options'}{$key};
-% $html .= qq!<OPTION VALUE="$key"!.
-% ( $plandata{$field} =~ /(^|, *)$key *(,|$)/ #XXX fix
-% ? ' SELECTED'
-% : ''
-% ).
-% '>'. $label;
-% }
-%
-% } else {
-% $html .= '<font color="#ff0000">warning: '.
-% "don't know how to retreive options for $field select field".
-% '</font>';
-% }
-% $html .= '</SELECT>';
-%
-% } elsif ( $href->{$field}{'type'} eq 'radio' ) {
-%
-% my $radio =
-% qq!<INPUT TYPE="radio" NAME="$field" onChange="fchanged(this)"!;
-%
-% foreach my $key ( keys %{ $href->{$field}{'options'} } ) {
-% my $label = $href->{$field}{'options'}{$key};
-% $html .= qq!$radio VALUE="$key"!.
-% ( $plandata{$field} =~ /(^|, *)$key *(,|$)/ #XXX fix
-% ? ' CHECKED'
-% : ''
-% ).
-% "> $label<BR>";
-% }
-%
-% }
-%
-% $html .= '</TD></TR>';
-% }
-% $html .= '</TABLE>';
-%
-% $html .= '<INPUT TYPE="hidden" NAME="plandata" VALUE="'.
-% join(',', keys %{ $href } ). '">'.
-% '<BR><BR>';
-%
-% $html .= '<INPUT TYPE="submit" VALUE="'.
-% ( $action eq 'Custom'
-% ? 'Customize package'
-% : ( $hashref->{pkgpart} ? "Apply changes" : "Add package" )
-% ).
-% '" onClick="fchanged(this)">';
-%
-% $html;
-%
-% },
-%);
-%
-%
-
-
-<BR><BR>Price plan <% $widget->html %>
-
-<% include('/elements/footer.html') %>
+<% include( 'elements/edit.html',
+ 'post_url' => popurl(1).'process/part_pkg.cgi',
+ 'name' => "Package definition",
+ 'table' => 'part_pkg',
+ #'viewall_dir' => 'browse',
+ 'viewall_url' => $p.'browse/part_pkg.cgi',
+ 'html_init' => include('/elements/init_overlib.html').
+ $freq_changed,
+ 'html_bottom' => $html_bottom,
+ 'new_hashref_callback' => $new_hashref_callback,
+ 'new_object_callback' => $new_object_callback,
+ 'new_callback' => $new_callback,
+ 'edit_callback' => $edit_callback,
+ 'error_callback' => $error_callback,
+
+ 'labels' => {
+ 'pkgpart' => 'Package Definition',
+ 'pkg' => 'Package (customer-visible)',
+ 'comment' => 'Comment (customer-hidden)',
+ 'classnum' => 'Package class',
+ 'promo_code' => 'Promotional code',
+ 'freq' => 'Recurring fee frequency',
+ 'setuptax' => 'Setup fee tax exempt',
+ 'recurtax' => 'Recurring fee tax exempt',
+ 'taxclass' => 'Tax class',
+ 'plan' => 'Price plan',
+ 'disabled' => 'Disable new orders',
+ 'pay_weight' => 'Payment weight',
+ 'credit_weight' => 'Credit weight',
+ 'agentnum' => '',
+ 'setup_fee' => 'Setup fee',
+ 'recur_fee' => 'Recurring fee',
+ 'bill_dst_pkgpart' => 'Include line item(s) from package',
+ 'svc_dst_pkgpart' => 'Include services of package',
+ },
+
+ 'fields' => [
+ { field=>'clone', type=>'hidden' },
+ { field=>'pkgnum', type=>'hidden' },
+
+ { type => 'columnstart' },
+
+ {field=>'pkg', type=>'text', size=>40 }, #32
+ {field=>'comment', type=>'text', size=>40 }, #32
+ {field=>'classnum', type=>'select-pkg_class' },
+ {field=>'disabled', type=>'checkbox', value=>'Y'},
+
+ { type => 'tablebreak-tr-title',
+ value => 'Pricing', #better name?
+ },
+ { field => 'plan',
+ type => 'selectlayers-select',
+ options => [ keys %plan_labels ],
+ labels => \%plan_labels,
+ },
+ { field => 'setup_fee',
+ type => 'money',
+ },
+ { field => 'freq',
+ type => 'part_pkg_freq',
+ onchange => 'freq_changed', #XXX enable recurring fee
+ },
+ { field => 'recur_fee',
+ type => 'money',
+ disabled => sub { $recur_disabled },
+ },
+
+ #price plan
+ #setup fee
+ #recurring frequency
+ #recurring fee (auto-disable)
+
+ { type => 'columnnext' },
+
+ {type=>'justtitle', value=>'Taxation' },
+ {field=>'setuptax', type=>'checkbox', value=>'Y'},
+ {field=>'recurtax', type=>'checkbox', value=>'Y'},
+ {field=>'classnum', type=>'select-taxclass' },
+ {field=>'taxproductnum', type=>'select-taxproduct' },
+
+ { type => 'tablebreak-tr-title',
+ value => 'Promotions', #XXX better name?
+ },
+ { field=>'promo_code', type=>'text', size=>15 },
+
+ { type => 'tablebreak-tr-title',
+ value => 'Line-item revenue recogition', #XXX better name?
+ },
+ { field=>'pay_weight', type=>'text', size=>6 },
+ { field=>'credit_weight', type=>'text', size=>6 },
+
+ { type => 'columnnext' },
+
+ { field=>'agent_type',
+ type => 'select-agent_types',
+ curr_value_callback => sub {
+ my($cgi, $object, $field) = @_;
+ #in the other callbacks..? hmm.
+ \@agent_type;
+ },
+ },
+
+ { type => 'columnend' },
+
+ { 'type' => 'tablebreak-tr-title',
+ 'value' => 'Pricing add-ons',
+ },
+ { 'field' => 'bill_dst_pkgpart',
+ 'type' => 'select-part_pkg',
+ 'm2_label' => 'Include line item(s) from package',
+ 'm2m_table' => 'part_pkg_link',
+ 'm2m_target_table' => 'part_pkg', #XXX actually just the method name...
+ 'm2m_dstcol' => 'dst_pkgpart',
+ 'm2m_static_or_something' => { 'link_type' => 'bill' }, #XXX
+ 'm2_error_callback' => sub { (); }, #XXX existing!
+ },
+
+ { type => 'tablebreak-tr-title',
+ value => 'Services',
+ },
+ { type => 'pkg_svc', },
+
+ { 'field' => 'svc_dst_pkgpart',
+ 'label' => 'Also include services from package: ',
+ 'type' => 'select-part_pkg',
+ 'm2_label' => 'Include services of package: ',
+ 'm2m_table' => 'part_pkg_link',
+ 'm2m_target_table' => 'part_pkg', #XXX actually just the method name...
+ 'm2m_dstcol' => 'dst_pkgpart',
+ 'm2m_static_or_something' => { 'link_type' => 'svc' }, #XXX
+ 'm2_error_callback' => sub { (); }, #XXX existing!
+ },
+
+ { type => 'tablebreak-tr-title',
+ value => 'Price plan options',
+ },
+
+ ],
+
+ )
+%>
<%init>
-if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) {
- $cgi->param('clone', $1);
-} else {
- $cgi->param('clone', '');
-}
-if ( $cgi->param('pkgnum') && $cgi->param('pkgnum') =~ /^(\d+)$/ ) {
- $cgi->param('pkgnum', $1);
-} else {
- $cgi->param('pkgnum', '');
-}
-
my $curuser = $FS::CurrentUser::CurrentUser;
die "access denied"
@@ -439,61 +148,264 @@ die "access denied"
|| $curuser->access_right('Edit global package definitions')
|| ( $cgi->param('pkgnum') && $curuser->access_right('Customize customer package') );
-my ($query) = $cgi->keywords;
+#XXX
+# - part_pkg.pm bits (need separate access methods not just part_pkg_link)
+# - tr-part_pkg_freq: month_increments_only (from price plans)
+# - test editing
+# - write edit bits for m2ms
+# - display add-ons in browse... yeah
+# -QIS- thank goodness
+# - test cloning
+# - test custom pricing
+#recur_flat->recur_fee migration, ugh
+# - move the selectlayer divs away from lame layer_callback
+
+#my ($query) = $cgi->keywords;
+#
+#my $part_pkg = '';
-my $conf = new FS::Conf;
-my $part_pkg = '';
my @agent_type = ();
my $tax_override;
-my @all_agent_types = map {$_->typenum} qsearch('agent_type',{});
-if ( $cgi->param('error') ) {
- $part_pkg = new FS::part_pkg ( {
- map { $_, scalar($cgi->param($_)) } fields('part_pkg')
- } );
- (@agent_type) = $cgi->param('agent_type');
- $tax_override = $cgi->param('tax_override');
-}
-my $action = '';
my $clone_part_pkg = '';
-my $pkgpart = '';
-if ( $cgi->param('clone') ) {
- $pkgpart = $cgi->param('clone');
- #$action = 'Custom Pricing';
- $action = 'Custom';
+
+my %options = ();
+my $recur_disabled = 1;
+my $error_callback = sub {
+ my($cgi, $object, $fields) = @_;
+ (@agent_type) = $cgi->param('agent_type');
+ $tax_override = $cgi->param('tax_override');
$clone_part_pkg= qsearchs('part_pkg', { 'pkgpart' => $cgi->param('clone') } );
- $part_pkg ||= $clone_part_pkg->clone;
- $part_pkg->disabled('Y') unless $cgi->param('error');
-} elsif ( $query && $query =~ /^(\d+)$/ ) {
- (@agent_type) = map {$_->typenum} qsearch('type_pkgs',{'pkgpart'=>$1})
- unless $part_pkg;
- unless ($part_pkg) {
- $tax_override =
+
+ $recur_disabled = $cgi->param('freq') ? 0 : 1;
+
+ #some false laziness w/process
+ $cgi->param('plan') =~ /^(\w+)$/ or die 'unparsable plan';
+ my $plan = $1;
+ my $options = $cgi->param($plan."__OPTIONS");
+ my @options = split(',', $options);
+ %options =
+ map { my $optionname = $_;
+ my $param = $plan."__$optionname";
+ my $value = join(', ', $cgi->param($param));
+ ( $optionname => $value );
+ }
+ @options;
+
+ $cgi->param($_, $options{$_}) foreach (qw( setup_fee recur_fee ));
+
+};
+
+my $new_hashref_callback = sub { { 'plan' => 'flat' }; };
+
+my $new_object_callback = sub {
+ my( $cgi, $hashref, $fields, $opt ) = @_;
+
+ if ( $cgi->param('clone') ) {
+ $opt->{action} = 'Custom';
+ $clone_part_pkg = qsearchs('part_pkg', { 'pkgpart' => $cgi->param('clone') } );
+ my $part_pkg = $clone_part_pkg->clone;
+ $part_pkg->disabled('Y');
+ %options = $clone_part_pkg->options;
+ $part_pkg;
+ } else {
+ FS::part_pkg->new( $hashref );
+ }
+
+};
+
+my $edit_callback = sub {
+ my( $cgi, $object, $fields ) = @_;
+
+ $recur_disabled = $object->freq ? 0 : 1;
+
+ (@agent_type) = map {$_->typenum} qsearch('type_pkgs',{'pkgpart'=>$1});
+ $tax_override =
join (",", map {$_->taxclassnum}
qsearch( 'part_pkg_taxoverride', {'pkgpart' => $1} )
);
+
# join (",", map {$_->taxclassnum}
# $part_pkg->part_pkg_taxrate( 'cch', $conf->config('defaultloc')
# );
# unless $tax_override;
+
+ %options = $object->options;
+
+};
+
+my $new_callback = sub {
+ my( $cgi, $object, $fields ) = @_;
+
+ my $conf = new FS::Conf;
+ if ( $conf->exists('agent_defaultpkg') ) {
+ #my @all_agent_types = map {$_->typenum} qsearch('agent_type',{});
+ @agent_type = map {$_->typenum} qsearch('agent_type',{});
}
- $part_pkg ||= qsearchs('part_pkg',{'pkgpart'=>$1});
- $pkgpart = $part_pkg->pkgpart;
-} else {
- unless ( $part_pkg ) {
- $part_pkg = new FS::part_pkg {};
- $part_pkg->plan('flat');
- @agent_type = @all_agent_types if $conf->exists('agent_defaultpkg');
-
- }
-}
-unless ( $part_pkg->plan ) { #backwards-compat
- $part_pkg->plan('flat');
- $part_pkg->plandata("setup_fee=". $part_pkg->setup. "\n".
- "recur_fee=". $part_pkg->recur. "\n");
-}
-$action ||= $part_pkg->pkgpart ? 'Edit' : 'Add';
-my $hashref = $part_pkg->hashref;
-my $taxproduct_description = $part_pkg->taxproduct_description;
+
+};
+
+my $freq_changed = <<'END';
+ <SCRIPT TYPE="text/javascript">
+
+ function freq_changed(what) {
+ var freq = what.options[what.selectedIndex].value;
+
+ if ( freq == '0' ) {
+ what.form.recur_fee.disabled = true;
+ what.form.recur_fee.style.backgroundColor = '#dddddd';
+ } else {
+ what.form.recur_fee.disabled = false;
+ what.form.recur_fee.style.backgroundColor = '#ffffff';
+ }
+
+ }
+
+ </SCRIPT>
+END
+
+tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
+
+tie my %plan_labels, 'Tie::IxHash',
+ map { $_ => ( $plans{$_}->{'shortname'} || $plans{$_}->{'name'} ) }
+ keys %plans;
+
+my $html_bottom = sub {
+ my( $object ) = @_;
+
+ #warn join("\n", map { "$_: $options{$_}" } keys %options ). "\n";
+
+ my $layer_callback = sub {
+
+ my $layer = shift;
+ my $html = ntable("#cccccc",2);
+
+ #$html .= '
+ # <TR>
+ # <TD ALIGN="right">Recurring fee frequency </TD>
+ # <TD><SELECT NAME="freq">
+ #';
+ #
+ #my @freq = keys %freq;
+ #@freq = grep { /^\d+$/ } @freq
+ #XXX this bit# # if exists($plans{$layer}->{'freq'}) && $plans{$layer}->{'freq'} eq 'm';
+ #foreach my $freq ( @freq ) {
+ # $html .= qq(<OPTION VALUE="$freq");
+ # $html .= ' SELECTED' if $freq eq $part_pkg->freq;
+ # $html .= ">$freq{$freq}";
+ #}
+ #$html .= '</SELECT></TD></TR>';
+
+ my $href = $plans{$layer}->{'fields'};
+ my @fields = exists($plans{$layer}->{'fieldorder'})
+ ? @{$plans{$layer}->{'fieldorder'}}
+ : keys %{ $href };
+
+ foreach my $field ( grep $_ !~ /^(setup|recur)_fee$/, @fields ) {
+
+ $html .= '<TR><TD ALIGN="right">'. $href->{$field}{'name'}. '</TD><TD>';
+
+ my $format = sub { shift };
+ $format = $href->{$field}{'format'} if exists($href->{$field}{'format'});
+
+ #XXX these should use elements/ fields... (or this whole thing should
+ #just use layer_fields instead of layer_callback)
+
+ if ( ! exists($href->{$field}{'type'}) ) {
+
+ $html .= qq!<INPUT TYPE="text" NAME="${layer}__$field" VALUE="!.
+ ( exists($options{$field})
+ ? &$format($options{$field})
+ : $href->{$field}{'default'} ).
+ qq!">!;
+
+ } elsif ( $href->{$field}{'type'} eq 'checkbox' ) {
+
+ $html .= qq!<INPUT TYPE="checkbox" NAME="${layer}__$field" VALUE=1 !.
+ ( exists($options{$field}) && $options{$field}
+ ? ' CHECKED'
+ : ''
+ ). '>';
+
+ } elsif ( $href->{$field}{'type'} =~ /^select/ ) {
+
+ $html .= '<SELECT';
+ $html .= ' MULTIPLE'
+ if $href->{$field}{'type'} eq 'select_multiple';
+ $html .= qq! NAME="${layer}__$field">!;
+
+ if ( $href->{$field}{'select_table'} ) {
+ foreach my $record (
+ qsearch( $href->{$field}{'select_table'},
+ $href->{$field}{'select_hash'} )
+ ) {
+ my $value = $record->getfield($href->{$field}{'select_key'});
+ $html .= qq!<OPTION VALUE="$value"!.
+ ( $options{$field} =~ /(^|, *)$value *(,|$)/ #XXX fix?
+ ? ' SELECTED'
+ : ''
+ ).
+ '>'. $record->getfield($href->{$field}{'select_label'});
+ }
+ } elsif ( $href->{$field}{'select_options'} ) {
+ foreach my $key ( keys %{ $href->{$field}{'select_options'} } ) {
+ my $label = $href->{$field}{'select_options'}{$key};
+ $html .= qq!<OPTION VALUE="$key"!.
+ ( $options{$field} =~ /(^|, *)$key *(,|$)/ #XXX fix?
+ ? ' SELECTED'
+ : ''
+ ).
+ '>'. $label;
+ }
+
+ } else {
+ $html .= '<font color="#ff0000">warning: '.
+ "don't know how to retreive options for $field select field".
+ '</font>';
+ }
+ $html .= '</SELECT>';
+
+ } elsif ( $href->{$field}{'type'} eq 'radio' ) {
+
+ my $radio =
+ qq!<INPUT TYPE="radio" NAME="${layer}__$field"!;
+
+ foreach my $key ( keys %{ $href->{$field}{'options'} } ) {
+ my $label = $href->{$field}{'options'}{$key};
+ $html .= qq!$radio VALUE="$key"!.
+ ( $options{$field} =~ /(^|, *)$key *(,|$)/ #XXX fix?
+ ? ' CHECKED'
+ : ''
+ ).
+ "> $label<BR>";
+ }
+
+ }
+
+ $html .= '</TD></TR>';
+ }
+ $html .= '</TABLE>';
+
+ $html .= qq(<INPUT TYPE="hidden" NAME="${layer}__OPTIONS" VALUE=").
+ join(',', keys %{ $href } ). '">';
+
+ $html;
+
+ };
+
+ my %selectlayers = (
+ field => 'plan',
+ options => [ keys %plan_labels ],
+ labels => \%plan_labels,
+ curr_value => $object->plan,
+ layer_callback => $layer_callback,
+ );
+
+ include('/elements/selectlayers.html', %selectlayers, 'layers_only'=>1 ).
+ '<SCRIPT TYPE="text/javascript">'.
+ include('/elements/selectlayers.html', %selectlayers, 'js_only'=>1 ).
+ '</SCRIPT>';
+
+};
</%init>
diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html
index d29ffcf..7964dfa 100644
--- a/httemplate/edit/process/elements/process.html
+++ b/httemplate/edit/process/elements/process.html
@@ -32,8 +32,13 @@ Example:
'clear_on_error' => [ 'form_field1', 'form_field2', ... ],
+ #pass an arrayref of hashrefs for multiple m2ms or m2names
+
'process_m2m' => { 'link_table' => 'link_table_name',
'target_table' => 'target_table_name',
+ #optional (see m2m_Common::process_m2m), if not specified
+ # all CGI params will be passed)
+ 'params' =>
},
'process_m2name' => { 'link_table' => 'link_table_name',
'link_static' => { 'column' => 'value' },
@@ -49,6 +54,10 @@ Example:
},
+ #checks CGI params and whatever else before much else runs
+ #return an error string or empty for no error
+ 'precheck_callback' => sub { my( $cgi ) = @_; },
+
#supplies arguments to insert() and replace()
# for use with tables that are FS::option_Common
'args_callback' => sub { my( $cgi, $object ) = @_; },
@@ -107,8 +116,8 @@ Example:
<% $cgi->redirect( $opt{'redirect'}. $pkeyvalue ) %>
%
%} else {
-%
-<% $cgi->redirect( popurl(3). ($opt{viewall_dir}||'search'). "/$table.html" ) %>
+% my $ext = $opt{'viewall_ext'} || 'html';
+<% $cgi->redirect( popurl(3). ($opt{viewall_dir}||'search'). "/$table.$ext" ) %>
%}
<%once>
@@ -121,6 +130,11 @@ my(%opt) = @_;
my $curuser = $FS::CurrentUser::CurrentUser;
+my $error = '';
+if ( $opt{'precheck_callback'} ) {
+ $error = &{ $opt{'precheck_callback'} }( $cgi );
+}
+
#false laziness w/edit.html
my $table = $opt{'table'};
my $class = "FS::$table";
@@ -146,7 +160,7 @@ if ( $pkeyvalue ) {
}
my %hash =
- map { my @entry = ( $_ => $cgi->param($_) );
+ map { my @entry = ( $_ => scalar($cgi->param($_)) );
$opt{'value_callback'} ? ( $_ => &{ $opt{'value_callback'} }( @entry ))
: ( @entry )
} @$fields;
@@ -168,7 +182,7 @@ if ($old && exists($opt{'copy_on_empty'})) {
}
}
-my $error = $new->check;
+$error ||= $new->check;
my @args = ();
if ( !$error && $opt{'args_callback'} ) {
@@ -192,28 +206,42 @@ if ( !$error ) {
if ( !$error && $opt{'process_m2m'} ) {
- if ( $opt{'debug'} ) {
- warn "$me processing m2m:\n". Dumper( %{ $opt{'process_m2m'} },
- 'params' => scalar($cgi->Vars),
- );
+ my @process_m2m = ref($opt{'process_m2m'}) eq 'ARRAY'
+ ? @{ $opt{'process_m2m'} }
+ : ( $opt{'process_m2m'} );
+
+ foreach my $process_m2m (@process_m2m) {
+
+ $process_m2m->{'params'} ||= scalar($cgi->Vars);
+
+ warn "$me processing m2m:\n". Dumper( %$process_m2m )
+ if $opt{'debug'};
+
+ $error = $new->process_m2m( %$process_m2m );
}
- $error = $new->process_m2m( %{ $opt{'process_m2m'} },
- 'params' => scalar($cgi->Vars),
- );
}
if ( !$error && $opt{'process_m2name'} ) {
- if ( $opt{'debug'} ) {
- warn "$me processing m2name:\n". Dumper( %{ $opt{'process_m2name'} },
- 'params' => scalar($cgi->Vars),
- );
+ my @process_m2name = ref($opt{'process_m2name'}) eq 'ARRAY'
+ ? @{ $opt{'process_m2name'} }
+ : ( $opt{'process_m2name'} );
+
+
+ foreach my $process_m2name (@process_m2name) {
+
+ if ( $opt{'debug'} ) {
+ warn "$me processing m2name:\n". Dumper( %{ $process_m2name },
+ 'params' => scalar($cgi->Vars),
+ );
+ }
+
+ $error = $new->process_m2name( %{ $process_m2name },
+ 'params' => scalar($cgi->Vars),
+ );
}
- $error = $new->process_m2name( %{ $opt{'process_m2name'} },
- 'params' => scalar($cgi->Vars),
- );
}
diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi
index 94bff0f..be6e2f8 100755
--- a/httemplate/edit/process/part_pkg.cgi
+++ b/httemplate/edit/process/part_pkg.cgi
@@ -1,124 +1,140 @@
-%if ( $error ) {
-% $dbh->rollback if $oldAutoCommit;
-% $cgi->param('error', $error );
-<% $cgi->redirect(popurl(2). "part_pkg.cgi?". $cgi->query_string ) %>
-%} elsif ( $custnum ) {
-% $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum") %>
-%} else {
-% $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-<% $cgi->redirect(popurl(3). "browse/part_pkg.cgi") %>
-%}
+<% include( 'elements/process.html',
+ #'debug' => 1,
+ 'table' => 'part_pkg',
+ 'viewall_dir' => 'browse',
+ 'viewall_ext' => 'cgi',
+ 'edit_ext' => 'cgi',
+ #XXX usable with cloning? #'agent_null_right' => 'Edit global package definitions',
+ 'precheck_callback' => $precheck_callback,
+ 'args_callback' => $args_callback,
+ 'process_m2m' => \@process_m2m,
+ 'debug' => 1,
+ )
+%>
<%init>
-my $dbh = dbh;
-my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
-my $pkgpart = $cgi->param('pkgpart');
-
-my $old = qsearchs('part_pkg',{'pkgpart'=>$pkgpart}) if $pkgpart;
-
-tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
-my $href = $plans{$cgi->param('plan')}->{'fields'};
-
-#fixup plandata
-my $error;
-my $plandata = $cgi->param('plandata');
-my @plandata = split(',', $plandata);
-$cgi->param('plandata',
- join('', map { my $parser = sub { shift };
- $parser = $href->{$_}{parse} if exists($href->{$_}{parse});
- my $value = join(', ', &$parser($cgi->param($_)));
- my $check = $href->{$_}{check};
- if ( $check && ! &$check($value) ) {
- $value = join(', ', $cgi->param($_));
- $error ||= "Illegal ". ($href->{$_}{name}||$_). ": $value";
- }
- "$_=$value\n";
- } @plandata )
-);
+die "access denied"
+ unless $curuser->access_right('Edit package definitions')
+ || $curuser->access_right('Edit global package definitions')
+ || ( ! $cgi->param('pkgpart') && $cgi->param('pkgnum') && $curuser->access_right('Customize customer package') );
-foreach (qw( setuptax recurtax disabled )) {
- $cgi->param($_, '') unless defined $cgi->param($_);
-}
+my $precheck_callback = sub {
+ my( $cgi ) = @_;
-my @agents;
-foreach ($cgi->param('agent_type')) {
- /^(\d+)$/;
- push @agents, $1 if $1;
-}
-$error = "At least one agent type must be specified."
- unless( scalar(@agents) ||
- $cgi->param('clone') && $cgi->param('clone') =~ /^\d+$/ ||
- !$pkgpart && $conf->exists('agent-defaultpkg')
- );
+ my $conf = new FS::Conf;
-$cgi->param('tax_override') =~ /^([\d,]+)$/;
-my (@tax_overrides) = (grep "$_", split (",", $1));
+ foreach (qw( setuptax recurtax disabled )) {
+ $cgi->param($_, '') unless defined $cgi->param($_);
+ }
-my $new = new FS::part_pkg ( {
- map {
- $_ => scalar($cgi->param($_));
- } fields('part_pkg')
-} );
+ return 'Must select a tax class'
+ if $cgi->param('taxclass') eq '(select)';
-my $oldAutoCommit = $FS::UID::AutoCommit;
-local $FS::UID::AutoCommit = 0;
+ my @agents = ();
+ foreach ($cgi->param('agent_type')) {
+ /^(\d+)$/;
+ push @agents, $1 if $1;
+ }
+ return "At least one agent type must be specified."
+ unless( scalar(@agents) ||
+ $cgi->param('clone') && $cgi->param('clone') =~ /^\d+$/ ||
+ !$cgi->param('pkgpart') && $conf->exists('agent-defaultpkg')
+ );
-my %pkg_svc = map { $_ => scalar($cgi->param("pkg_svc$_")) }
- map { $_->svcpart }
- qsearch('part_svc', {} );
+ return '';
-my $curuser = $FS::CurrentUser::CurrentUser;
+};
my $custnum = '';
-if ( $error ) {
-
- # fall through
-
-} elsif ( $cgi->param('taxclass') eq '(select)' ) {
- $error = 'Must select a tax class';
+my $args_callback = sub {
+ my( $cgi, $new ) = @_;
+
+ my @args = ( 'primary_svc' => scalar($cgi->param('pkg_svc_primary')) );
+
+ ##
+ #options
+ ##
+
+ $cgi->param('plan') =~ /^(\w+)$/ or die 'unparsable plan';
+ my $plan = $1;
+
+ tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
+ my $href = $plans{$plan}->{'fields'};
+
+ my $error = '';
+ my $options = $cgi->param($plan."__OPTIONS");
+ my @options = split(',', $options);
+ my %options =
+ map { my $optionname = $_;
+ my $param = $plan."__$optionname";
+ my $parser = exists($href->{$optionname}{parse})
+ ? $href->{$optionname}{parse}
+ : sub { shift };
+ my $value = join(', ', &$parser($cgi->param($param)));
+ my $check = $href->{$optionname}{check};
+ if ( $check && ! &$check($value) ) {
+ $value = join(', ', $cgi->param($param));
+ $error ||= "Illegal ".
+ ($href->{$optionname}{name}||$optionname). ": $value";
+ }
+ ( $optionname => $value );
+ }
+ @options;
+
+ $options{$_} = scalar($cgi->param($_)) for (qw( setup_fee recur_fee ));
+
+ push @args, 'options' => \%options;
+
+ ###
+ #pkg_svc
+ ###
+
+ my %pkg_svc = map { $_ => scalar($cgi->param("pkg_svc$_")) }
+ map { $_->svcpart }
+ qsearch('part_svc', {} );
+
+ push @args, 'pkg_svc' => \%pkg_svc;
+
+ ###
+ # cust_pkg and custnum_ref (inserts only)
+ ###
+ unless ( $cgi->param('pkgpart') ) {
+ push @args, 'cust_pkg' => scalar($cgi->param('pkgnum')),
+ 'custnum_ref' => \$custnum;
+ }
+
+ @args;
+
+};
-} elsif ( $pkgpart ) {
-
- die "access denied"
- unless $curuser->access_right('Edit package definitions')
- || $curuser->access_right('Edit global package definitions');
-
- $error = $new->replace( $old,
- pkg_svc => \%pkg_svc,
- primary_svc => scalar($cgi->param('pkg_svc_primary')),
- );
-} else {
+$cgi->param('tax_override') =~ /^([\d,]+)$/;
+my (@tax_overrides) = (grep "$_", split (",", $1));
- die "access denied"
- unless $curuser->access_right('Edit package definitions')
- || $curuser->access_right('Edit global package definitions')
- || ( $cgi->param('pkgnum') && $curuser->access_right('Customize customer package') );
+my @process_m2m = (
+ {
+ 'link_table' => 'part_pkg_taxoverride',
+ 'target_table' => 'tax_class',
+ 'params' => \@tax_overrides,
+ }
+);
- $error = $new->insert( pkg_svc => \%pkg_svc,
- primary_svc => scalar($cgi->param('pkg_svc_primary')),
- cust_pkg => $cgi->param('pkgnum'),
- custnum_ref => \$custnum,
- );
- $pkgpart = $new->pkgpart;
-}
+my $conf = new FS::Conf;
-unless ( $error || $conf->exists('agent_defaultpkg') ) {
- $error = $new->process_m2m(
+if ( $cgi->param('pkgpart') || ! $conf->exists('agent_defaultpkg') ) {
+ my @agents = ();
+ foreach ($cgi->param('agent_type')) {
+ /^(\d+)$/;
+ push @agents, $1 if $1;
+ }
+ warn "AGENTS: @agents";
+ push @process_m2m, {
'link_table' => 'type_pkgs',
'target_table' => 'agent_type',
'params' => \@agents,
- );
-}
-
-unless ( $error ) {
- $error = $new->process_m2m(
- 'link_table' => 'part_pkg_taxoverride',
- 'target_table' => 'tax_class',
- 'params' => \@tax_overrides,
- );
+ };
}
</%init>
diff --git a/httemplate/elements/select-agent_types.html b/httemplate/elements/select-agent_types.html
new file mode 100644
index 0000000..e56fee4
--- /dev/null
+++ b/httemplate/elements/select-agent_types.html
@@ -0,0 +1,30 @@
+%# if ( $cgi->param('clone') ) { #XXX
+% if ( $opt{'disable'} ) {
+
+ <INPUT TYPE="hidden" NAME="agent_type" VALUE="">
+
+% } elsif ( scalar(@all_agent_types) == 1) {
+
+ <INPUT TYPE="hidden" NAME="agent_type" VALUE="<% $all_agent_types[0] %>">
+
+% } else {
+
+ <% include( 'select-table.html',
+ 'element_name' => 'agent_type',
+ 'table' => 'agent_type',
+ 'name_col' => 'atype',
+ #'value' => \@agent_type,
+ 'element_etc' => 'size="10"',
+ %opt,
+ 'multiple' => '1', #cause edit.html is dum
+ )
+ %>
+
+% }
+<%init>
+
+my %opt = @_;
+
+my @all_agent_types = map {$_->typenum} qsearch('agent_type',{});
+
+</%init>
diff --git a/httemplate/elements/select-taxproduct.html b/httemplate/elements/select-taxproduct.html
index f2ae9eb..dc7ab97 100644
--- a/httemplate/elements/select-taxproduct.html
+++ b/httemplate/elements/select-taxproduct.html
@@ -20,4 +20,7 @@
my %opt = @_;
+$opt{'taxproduct_description'} ||= $opt{'object'}->taxproduct_description
+ if $opt{'object'};
+
</%init>
diff --git a/httemplate/elements/selectlayers.html b/httemplate/elements/selectlayers.html
index 3026217..82f5dd1 100644
--- a/httemplate/elements/selectlayers.html
+++ b/httemplate/elements/selectlayers.html
@@ -44,6 +44,10 @@ Example:
...
},
+ #or manual control, instead of layer_fields and layer_values above
+ #called with args: my( $layer, $layer_fields, $layer_values, $layer_prefix )
+ 'layer_callback' =>
+
'html_between => '', #optional HTML displayed between the SELECT and the
#layers, scalar or coderef ('field' passed as a param)
'onchange' => '', #javascript code run when the SELECT changes
@@ -129,7 +133,7 @@ Example:
%>"
>
- <% layer_callback($layer, $layer_fields, $layer_values, $layer_prefix) %>
+ <% &{$layer_callback}($layer, $layer_fields, $layer_values, $layer_prefix) %>
</DIV>
@@ -165,6 +169,8 @@ my $layer_fields = $opt{layer_fields};
my $layer_values = $opt{layer_values};
my $layer_prefix = $opt{layer_prefix};
+my $layer_callback = $opt{layer_callback} || \&layer_callback;
+
sub layer_callback {
my( $layer, $layer_fields, $layer_values, $layer_prefix ) = @_;
diff --git a/httemplate/elements/tr-input-text.html b/httemplate/elements/tr-input-text.html
index 49ae166..f71f2f7 100644
--- a/httemplate/elements/tr-input-text.html
+++ b/httemplate/elements/tr-input-text.html
@@ -35,6 +35,11 @@ my $maxlength = $opt{'maxlength'}
? 'MAXLENGTH="'. $opt{'maxlength'}. '"'
: '';
+$opt{'disabled'} = &{ $opt{'disabled'} }( \%opt )
+ if ref($opt{'disabled'}) eq 'CODE';
+$opt{'disabled'} = 'DISABLED'
+ if $opt{'disabled'} && $opt{'disabled'} !~ /disabled/i; # uuh... yeah?
+
my @style = ();
push @style, 'text-align: '. $opt{'text-align'}
@@ -43,9 +48,6 @@ push @style, 'text-align: '. $opt{'text-align'}
push @style, 'background-color: #dddddd'
if $opt{'disabled'};
-$opt{'disabled'} = 'DISABLED'
- if $opt{'disabled'} && $opt{'disabled'} !~ /disabled/i; # uuh... yeah?
-
my $style = scalar(@style) ? 'STYLE="'. join(';', @style). '"' : '';
my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
diff --git a/httemplate/elements/tr-part_pkg_freq.html b/httemplate/elements/tr-part_pkg_freq.html
index 98b7da5..649f8a2 100644
--- a/httemplate/elements/tr-part_pkg_freq.html
+++ b/httemplate/elements/tr-part_pkg_freq.html
@@ -1,15 +1,10 @@
-<% include('tr-td-label.html', @_ ) %>
-
- <TD>
- <SELECT NAME="freq">
-% foreach my $freq ( @freq ) {
- <OPTION VALUE="<% $freq %>" <% $freq eq $curr_value ? 'SELECTED' : '' %>><% $freq{$freq} %>
-% }
- </SELECT>
- </TD>
-
-</TR>
-
+<% include('tr-select.html', @_,
+ 'field' => 'freq',
+ 'options' => \@freq,
+ 'labels' => \%freq,
+ 'curr_value' => $curr_value,
+ )
+%>
<%init>
my %opt = @_;
diff --git a/httemplate/elements/tr-pkg_svc.html b/httemplate/elements/tr-pkg_svc.html
new file mode 100644
index 0000000..4c8a839
--- /dev/null
+++ b/httemplate/elements/tr-pkg_svc.html
@@ -0,0 +1,93 @@
+<TR>
+ <TD BGCOLOR="#e8e8e8" COLSPAN=99>
+
+<% itable('', 4, 1) %><TR><TD VALIGN="top">
+<% $thead %>
+
+%foreach my $part_svc ( @part_svc ) {
+% my $svcpart = $part_svc->svcpart;
+% my $pkg_svc = $pkg_svc{$svcpart}
+% || new FS::pkg_svc ( {
+% 'pkgpart' => $pkgpart,
+% 'svcpart' => $svcpart,
+% 'quantity' => 0,
+% 'primary_svc' => '',
+% } );
+% if ( $cgi->param('error') ) {
+% my $primary_svc = ( $pkg_svc->primary_svc =~ /^Y/i );
+% my $pkg_svc_primary = scalar($cgi->param('pkg_svc_primary'));
+% $pkg_svc->primary_svc('')
+% if $primary_svc && $pkg_svc_primary != $svcpart;
+% $pkg_svc->primary_svc('Y')
+% if ! $primary_svc && $pkg_svc_primary == $svcpart;
+% }
+%
+% push @fixups, "pkg_svc$svcpart";
+%
+% my $quan = 0;
+% if ( $cgi->param("pkg_svc$svcpart") =~ /^\s*(\d+)\s*$/ ) {
+% $quan = $1;
+% } elsif ( $pkg_svc->quantity ) {
+% $quan = $pkg_svc->quantity;
+% }
+
+ <TR>
+ <TD>
+ <INPUT TYPE="text" NAME="pkg_svc<% $svcpart %>" SIZE=4 MAXLENGTH=3 VALUE="<% $quan %>">
+ </TD>
+
+ <TD ALIGN="center">
+ <INPUT TYPE="radio" NAME="pkg_svc_primary" VALUE="<% $svcpart %>" <% $pkg_svc->primary_svc =~ /^Y/i ? ' CHECKED' : '' %>>
+ </TD>
+
+ <TD>
+ <A HREF="part_svc.cgi?<% $part_svc->svcpart %>"><% $part_svc->svc %></A> <% $part_svc->disabled =~ /^Y/i ? ' (DISABLED' : '' %>
+ </TD>
+ </TR>
+% foreach ( 1 .. $columns-1 ) {
+% if ( $count == int( $_ * scalar(@part_svc) / $columns ) ) {
+%
+
+ </TABLE></TD><TD VALIGN="top"><% $thead %>
+% }
+% }
+% $count++;
+%
+% }
+
+</TR></TABLE></TD></TR></TABLE>
+
+ </TD>
+</TR>
+
+<%init>
+
+my %opt = @_;
+my $cgi = $opt{'cgi'};
+
+my $thead = "\n\n". ntable('#cccccc', 2).
+ '<TR><TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Quan.</FONT></TH>'.
+ '<TH BGCOLOR="#dcdcdc"><FONT SIZE=-2>Primary</FONT></TH>'.
+ '<TH BGCOLOR="#dcdcdc">Service</TH></TR>';
+
+my $part_pkg = $opt{'object'};
+my $pkgpart = $part_pkg->pkgpart;
+
+my $where = "WHERE disabled IS NULL OR disabled = ''";
+if ( $pkgpart ) {
+ $where .= " OR 0 < ( SELECT quantity FROM pkg_svc
+ WHERE pkg_svc.svcpart = part_svc.svcpart
+ AND pkgpart = $pkgpart
+ )";
+}
+my @part_svc = qsearch('part_svc', {}, '', $where);
+
+#my $q_part_pkg = $clone_part_pkg || $part_pkg;
+#my %pkg_svc = map { $_->svcpart => $_ } $q_part_pkg->pkg_svc;
+my %pkg_svc = map { $_->svcpart => $_ } $part_pkg->pkg_svc;
+
+my @fixups = ();
+my $count = 0;
+my $columns = 3;
+
+</%init>
diff --git a/httemplate/elements/tr-select-agent_types.html b/httemplate/elements/tr-select-agent_types.html
new file mode 100644
index 0000000..29ac7f1
--- /dev/null
+++ b/httemplate/elements/tr-select-agent_types.html
@@ -0,0 +1,19 @@
+% unless ( $opt{'disable'} || scalar(@all_agent_types) == 1 ) {
+
+<% include('/elements/tr-justtitle.html', value=>'Agent (reseller) types') %>
+
+% }
+
+<TR>
+ <TD COLSPAN=2>
+ <% include('select-agent_types.html', %opt) %>
+ </TD>
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+my @all_agent_types = map {$_->typenum} qsearch('agent_type',{});
+
+</%init>
diff --git a/httemplate/elements/tr-title.html b/httemplate/elements/tr-title.html
index 6e2f58f..8517737 100644
--- a/httemplate/elements/tr-title.html
+++ b/httemplate/elements/tr-title.html
@@ -2,14 +2,4 @@
<TD BGCOLOR="#e8e8e8" COLSPAN=2>&nbsp;</TD>
</TR>
-<TR>
- <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left">
- <FONT SIZE="+1"><% $opt{value} %></FONT>
- </TH>
-</TR>
-
-<%init>
-
-my %opt = @_;
-
-</%init>
+<% include('tr-justtitle.html', @_) %>