diff options
author | jeff <jeff> | 2008-08-28 07:38:40 +0000 |
---|---|---|
committer | jeff <jeff> | 2008-08-28 07:38:40 +0000 |
commit | 669bd6b315633c99ac61441198c2c4e45401c36b (patch) | |
tree | af72b9ad7bf116b147700f63f5b2895b0c9c9e5b | |
parent | 2c37cd91ea7951b70ea5d420e989128d78ce0a80 (diff) |
multiple usage classes checkpoint
-rw-r--r-- | FS/FS/Schema.pm | 4 | ||||
-rw-r--r-- | FS/FS/cust_bill_pkg.pm | 69 | ||||
-rw-r--r-- | FS/FS/cust_main.pm | 196 | ||||
-rw-r--r-- | FS/FS/part_pkg.pm | 97 | ||||
-rw-r--r-- | FS/FS/part_pkg/voip_cdr.pm | 8 | ||||
-rwxr-xr-x | httemplate/browse/part_pkg_taxproduct.cgi | 12 | ||||
-rwxr-xr-x | httemplate/edit/part_pkg.cgi | 107 | ||||
-rw-r--r-- | httemplate/edit/part_pkg_taxoverride.html | 5 | ||||
-rwxr-xr-x | httemplate/edit/process/part_pkg.cgi | 23 | ||||
-rw-r--r-- | httemplate/elements/select-taxoverride.html | 26 | ||||
-rw-r--r-- | httemplate/elements/select-taxproduct.html | 44 | ||||
-rw-r--r-- | httemplate/elements/tr-select-taxoverride.html | 18 | ||||
-rw-r--r-- | httemplate/elements/tr-select-taxproduct.html | 18 |
13 files changed, 519 insertions, 108 deletions
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 3bcd078ab..2043b45ec 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -502,6 +502,7 @@ sub tables_hashref { 'unitrecur', @money_typen, '', '', 'duplicate', 'char', 'NULL', 1, '', '', 'post_total', 'char', 'NULL', 1, '', '', + 'type', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'billpkgnum', 'unique' => [], @@ -514,7 +515,9 @@ sub tables_hashref { 'billpkgnum', 'int', 'NULL', '', '', '', # should not be nullable 'pkgnum', 'int', 'NULL', '', '', '', # deprecated 'invnum', 'int', 'NULL', '', '', '', # deprecated + 'amount', @money_typen, '', '', 'format', 'char', 'NULL', 1, '', '', + 'classnum', 'char', 'NULL', 1, '', '', 'detail', 'varchar', '', $char_d, '', '', ], 'primary_key' => 'detailnum', @@ -1103,6 +1106,7 @@ sub tables_hashref { 'taxoverridenum', 'serial', '', '', '', '', 'pkgpart', 'serial', '', '', '', '', 'taxclassnum', 'serial', '', '', '', '', + 'usage_class', 'varchar', 'NULL', $char_d, '', '', ], 'primary_key' => 'taxoverridenum', 'unique' => [], diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm index d3bf65bcb..b481e92a7 100644 --- a/FS/FS/cust_bill_pkg.pm +++ b/FS/FS/cust_bill_pkg.pm @@ -47,6 +47,8 @@ supported: =item pkgnum - package (see L<FS::cust_pkg>) or 0 for the special virtual sales tax package, or -1 for the virtual line item (itemdesc is used for the line) =item pkgpart_override - optional package definition (see L<FS::part_pkg>) override +=item type - can be set to U for usage; more later + =item setup - setup fee =item recur - recurring fee @@ -137,6 +139,8 @@ sub insert { 'billpkgnum' => $self->billpkgnum, 'format' => (ref($detail) ? $detail->[0] : '' ), 'detail' => (ref($detail) ? $detail->[1] : $detail ), + 'charge' => (ref($detail) ? $detail->[2] : '' ), + 'classnum' => (ref($detail) ? $detail->[3] : '' ), }; $error = $cust_bill_pkg_detail->insert; if ( $error ) { @@ -195,6 +199,7 @@ sub check { || $self->ut_textn('section') || $self->ut_enum('duplicate', [ '', 'Y' ]) || $self->ut_enum('post_total', [ '', 'Y' ]) + || $self->ut_enum('type', [ '', 'U' ]) #only usage for now ; return $error if $error; @@ -458,6 +463,70 @@ sub separate_cdr { $self->pkgnum && $self->section ne $self->part_pkg->categoryname; } +=item usage CLASSNUM + +Returns the amount of the charge associated with usage class CLASSNUM if +CLASSNUM is defined. Otherwise returns the total charge associated with +usage. + +=cut + +sub usage { + my( $self, $classnum ) = @_; + my $sum = 0; + my @values = (); + + if ( $self->get('details') ) { + + @values = + map { $_->[2] } + grep { ref($_) && ( defined($classnum) ? $_->[3] eq $classnum : 1 ) } + $self->get('details'); + + }else{ + + my $hashref = { 'billpkgnum' => $self->billpkgnum }; + $hashref->{ 'classnum' } = $classnum if defined($classnum); + @values = map { $_->charge } qsearch('cust_bill_pkg_detail', $hashref); + + } + + foreach ( @values ) { + $sum += $_ if $_; + } + $sum; +} + +=item usage_classes + +Returns a list of usage classnums associated with this invoice line's +details. + +=cut + +sub usage_classes { + my( $self ) = @_; + + if ( $self->get('details') ) { + + my %seen = (); + foreach my $detail ( grep { ref($_) } @{$self->get('details')} ) { + $seen{ $detail->[3] } = 1; + } + keys %seen; + + }else{ + + map { $_->classnum } + qsearch({ table => 'cust_bill_pkg_detail', + hashref => { billpkgnum => $self->billpkgnum }, + select => 'DISTINCT classnum', + }); + + } + +} + =back =head1 BUGS diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index cc664f30b..b72079cf1 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -2047,6 +2047,7 @@ Used in conjunction with the I<time> option, this option specifies the date of f sub bill { my( $self, %options ) = @_; return '' if $self->payby eq 'COMP'; + local $DEBUG = 1; warn "$me bill customer ". $self->custnum. "\n" if $DEBUG; @@ -2460,7 +2461,6 @@ sub _make_lines { }; $cust_bill_pkg->pkgpart_override($part_pkg->pkgpart) unless $part_pkg->pkgpart == $real_pkgpart; - push @$cust_bill_pkgs, $cust_bill_pkg; $$total_setup += $setup; $$total_recur += $recur; @@ -2471,7 +2471,14 @@ sub _make_lines { unless ( $self->tax =~ /Y/i || $self->payby eq 'COMP' ) { - $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg); + #some garbage disappears on cust_bill_pkg refactor + my $err_or_cust_bill_pkg = + $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg); + + return $err_or_cust_bill_pkg + unless ( ref($err_or_cust_bill_pkg) ); + + push @$cust_bill_pkgs, @$err_or_cust_bill_pkg; } #unless $self->tax =~ /Y/i || $self->payby eq 'COMP' @@ -2493,7 +2500,6 @@ sub _make_lines { $cust_bill_pkg->pkgpart_override($part_pkg->pkgpart) unless $part_pkg->pkgpart == $real_pkgpart; - push @$appended_cust_bill_pkg, $cust_bill_pkg; unless ($cust_bill_pkg->duplicate) { $$total_setup += $cust_bill_pkg->setup; @@ -2505,7 +2511,14 @@ sub _make_lines { unless ( $self->tax =~ /Y/i || $self->payby eq 'COMP' ) { - $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg); + #some garbage disappears on cust_bill_pkg refactor + my $err_or_cust_bill_pkg = + $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg); + + return $err_or_cust_bill_pkg + unless ( ref($err_or_cust_bill_pkg) ); + + push @$appended_cust_bill_pkg, @$err_or_cust_bill_pkg; } #unless $self->tax =~ /Y/i || $self->payby eq 'COMP' } @@ -2520,39 +2533,35 @@ sub _handle_taxes { my $taxlisthash = shift; my $cust_bill_pkg = shift; - my @taxes = (); - my @taxoverrides = $part_pkg->part_pkg_taxoverride; + my %cust_bill_pkg = (); + my %taxes = (); my $prefix = ( $conf->exists('tax-ship_address') && length($self->ship_last) ) ? 'ship_' : ''; + my @classes; + push @classes, $cust_bill_pkg->usage_classes if $cust_bill_pkg->type eq 'U'; + push @classes, 'setup' if $cust_bill_pkg->setup; + push @classes, 'recur' if $cust_bill_pkg->recur; + if ( $conf->exists('enable_taxproducts') - && (scalar(@taxoverrides) || $part_pkg->taxproductnum ) + && (scalar($part_pkg->part_pkg_taxoverride) || $part_pkg->has_taxproduct) ) { - my @taxclassnums = (); - my $geocode = $self->geocode('cch'); - - if ( scalar( @taxoverrides ) ) { - @taxclassnums = map { $_->taxclassnum } @taxoverrides; - }elsif ( $part_pkg->taxproductnum ) { - @taxclassnums = map { $_->taxclassnum } - $part_pkg->part_pkg_taxrate('cch', $geocode); + foreach my $class (@classes) { + my $err_or_ref = $self->_gather_taxes( $part_pkg, $class, $prefix ); + return $err_or_ref unless ref($err_or_ref); + $taxes{$class} = $err_or_ref; } - my $extra_sql = - "AND (". - join(' OR ', map { "taxclassnum = $_" } @taxclassnums ). ")"; - - @taxes = qsearch({ 'table' => 'tax_rate', - 'hashref' => { 'geocode' => $geocode, }, - 'extra_sql' => $extra_sql, - }) - if scalar(@taxclassnums); - + unless (exists $taxes{''}) { + my $err_or_ref = $self->_gather_taxes( $part_pkg, '', $prefix ); + return $err_or_ref unless ref($err_or_ref); + $taxes{''} = $err_or_ref; + } }else{ @@ -2561,7 +2570,7 @@ sub _handle_taxes { $taxhash{'taxclass'} = $part_pkg->taxclass; - @taxes = qsearch( 'cust_main_county', \%taxhash ); + my @taxes = qsearch( 'cust_main_county', \%taxhash ); unless ( @taxes ) { $taxhash{'taxclass'} = ''; @@ -2574,39 +2583,132 @@ sub _handle_taxes { @taxes = qsearch( 'cust_main_county', \%taxhash ); } - } #if $conf->exists('enable_taxproducts') + $taxes{''} = [ @taxes ]; + $taxes{'setup'} = [ @taxes ]; + $taxes{'recur'} = [ @taxes ]; + $taxes{$_} = [ @taxes ] foreach (@classes); - # maybe eliminate this entirely, along with all the 0% records - unless ( @taxes ) { - my $error; - if ( $conf->exists('enable_taxproducts') ) { - $error = - "fatal: can't find tax rate for zip/taxproduct/pkgpart ". - join('/', ( map $self->get("$prefix$_"), - qw(zip) - ), - $part_pkg->taxproduct_description, - $part_pkg->pkgpart ). "\n"; - } else { - $error = + # maybe eliminate this entirely, along with all the 0% records + unless ( @taxes ) { + return "fatal: can't find tax rate for state/county/country/taxclass ". join('/', ( map $self->get("$prefix$_"), qw(state county country) ), $part_pkg->taxclass ). "\n"; } - return $error; + + } #if $conf->exists('enable_taxproducts') + + # XXX all this goes away with cust_bill_pay refactor + + $cust_bill_pkg{setup} = $cust_bill_pkg if $cust_bill_pkg->setup; + $cust_bill_pkg{recur} = $cust_bill_pkg if $cust_bill_pkg->recur; + + #split setup and recur + if ($cust_bill_pkg->setup && $cust_bill_pkg->recur) { + my $cust_bill_pkg_recur = new FS::cust_bill_pkg { $cust_bill_pkg->hash }; + $cust_bill_pkg->set('details', []); + $cust_bill_pkg->recur(0); + $cust_bill_pkg->type(''); + $cust_bill_pkg{recur} = $cust_bill_pkg_recur; + } + + #split usage from recur + my $usage = $cust_bill_pkg->usage; + if ($usage) { + my $cust_bill_pkg_usage = + new FS::cust_bill_pkg { $cust_bill_pkg{recur}->hash }; + $cust_bill_pkg_usage->recur($usage); + $cust_bill_pkg{recur}->recur( $cust_bill_pkg{recur}->recur - $usage ); + $cust_bill_pkg{recur}->type( '' ); + $cust_bill_pkg{''} = $cust_bill_pkg_usage; + } + + #subdivide usage by usage_class + if (exists($cust_bill_pkg{''})) { + foreach my $class (grep {$_} @classes) { + my $usage = $cust_bill_pkg{''}->usage($class); + my $cust_bill_pkg_usage = + new FS::cust_bill_pkg { $cust_bill_pkg{''}->hash }; + $cust_bill_pkg_usage->recur($usage); + $cust_bill_pkg{''}->recur( $cust_bill_pkg{''}->recur - $usage ); + $cust_bill_pkg{$class} = $cust_bill_pkg_usage; + } + $cust_bill_pkg{''}->set('details', []); + delete $cust_bill_pkg{''} unless $cust_bill_pkg{''}->recur; } - foreach my $tax ( @taxes ) { - my $taxname = ref( $tax ). ' '. $tax->taxnum; - if ( exists( $taxlisthash->{ $taxname } ) ) { - push @{ $taxlisthash->{ $taxname } }, $cust_bill_pkg; - }else{ - $taxlisthash->{ $taxname } = [ $tax, $cust_bill_pkg ]; + foreach my $key (keys %cust_bill_pkg) { + my @taxes = @{ $taxes{$key} }; + my $cust_bill_pkg = $cust_bill_pkg{$key}; + + foreach my $tax ( @taxes ) { + my $taxname = ref( $tax ). ' '. $tax->taxnum; + if ( exists( $taxlisthash->{ $taxname } ) ) { + push @{ $taxlisthash->{ $taxname } }, $cust_bill_pkg; + }else{ + $taxlisthash->{ $taxname } = [ $tax, $cust_bill_pkg ]; + } } } + # sort setup,recur,'', and the rest numeric && return + my @result = map { $cust_bill_pkg{$_} } + sort { my $ad = ($a=~/^\d+$/); my $bd = ($b=~/^\d+$/); + ( $ad cmp $bd ) || ( $ad ? $a<=>$b : $b cmp $a ) + } + keys %cust_bill_pkg; + + \@result; +} + +sub _gather_taxes { + my $self = shift; + my $part_pkg = shift; + my $class = shift; + my $prefix = shift; + + my @taxes = (); + my $geocode = $self->geocode('cch'); + + my @taxclassnums = map { $_->taxclassnum } + $part_pkg->part_pkg_taxoverride($class); + + unless (@taxclassnums) { + @taxclassnums = map { $_->taxclassnum } + $part_pkg->part_pkg_taxrate('cch', $geocode, $class); + } + warn "Found taxclassnum values of ". join(',', @taxclassnums) + if $DEBUG; + + my $extra_sql = + "AND (". + join(' OR ', map { "taxclassnum = $_" } @taxclassnums ). ")"; + + @taxes = qsearch({ 'table' => 'tax_rate', + 'hashref' => { 'geocode' => $geocode, }, + 'extra_sql' => $extra_sql, + }) + if scalar(@taxclassnums); + + # maybe eliminate this entirely, along with all the 0% records + unless ( @taxes ) { + return + "fatal: can't find tax rate for zip/taxproduct/pkgpart ". + join('/', ( map $self->get("$prefix$_"), + qw(zip) + ), + $part_pkg->taxproduct_description, + $part_pkg->pkgpart ). "\n"; + } + + warn "Found taxes ". + join(',', map{ ref($_). " ". $_->get($_->primary_key) } @taxes). "\n" + if $DEBUG; + + [ @taxes ]; + } =item collect OPTIONS diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index 3b1cbed16..57145fdd9 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -801,19 +801,84 @@ sub _self_and_linked { ); } -=item part_pkg_taxoverride +=item part_pkg_taxoverride [ CLASS ] Returns all associated FS::part_pkg_taxoverride objects (see -L<FS::part_pkg_taxoverride>). +L<FS::part_pkg_taxoverride>). Limits the returned set to those +of class CLASS if defined. Class may be one of 'setup', 'recur', +the empty string (default), or a usage class number (see L<FS::usage_class>). +When a class is specified, the empty string class (default) is returned +if no more specific values exist. =cut sub part_pkg_taxoverride { my $self = shift; - qsearch('part_pkg_taxoverride', { 'pkgpart' => $self->pkgpart } ); + my $class = shift; + + my $hashref = { 'pkgpart' => $self->pkgpart }; + $hashref->{'usage_class'} = $class if defined($class); + my @overrides = qsearch('part_pkg_taxoverride', $hashref ); + + unless ( scalar(@overrides) || !defined($class) || !$class ){ + $hashref->{'usage_class'} = ''; + @overrides = qsearch('part_pkg_taxoverride', $hashref ); + } + + @overrides; +} + +=item has_taxproduct + +Returns true if this package has any taxproduct associated with it. + +=cut + +sub has_taxproduct { + my $self = shift; + + $self->taxproductnum || + scalar(grep { $_ =~/^usage_taxproductnum_/ } keys %{ {$self->options} } ) + +} + + +=item taxproduct [ CLASS ] + +Returns the associated tax product for this package definition (see +L<FS::part_pkg_taxproduct>). CLASS may be one of 'setup', 'recur' or +the usage classnum (see L<FS::usage_class>). Returns the default +tax product for this record if the more specific CLASS value does +not exist. + +=cut + +sub taxproduct { + my $self = shift; + my $class = shift; + + my $part_pkg_taxproduct; + + my $taxproductnum = $self->taxproductnum; + if ($class) { + my $class_taxproductnum = $self->option("usage_taxproductnum_$class", 1); + $taxproductnum = $class_taxproductnum + if $class_taxproductnum + } + + $part_pkg_taxproduct = + qsearchs( 'part_pkg_taxproduct', { 'taxproductnum' => $taxproductnum } ); + + unless ($part_pkg_taxproduct || $taxproductnum eq $self->taxproductnum ) { + $taxproductnum = $self->taxproductnum; + $part_pkg_taxproduct = + qsearchs( 'part_pkg_taxproduct', { 'taxproductnum' => $taxproductnum } ); + } + + $part_pkg_taxproduct; } -=item taxproduct_description +=item taxproduct_description [ CLASS ] Returns the description of the associated tax product for this package definition (see L<FS::part_pkg_taxproduct>). @@ -822,26 +887,24 @@ definition (see L<FS::part_pkg_taxproduct>). sub taxproduct_description { my $self = shift; - my $part_pkg_taxproduct = - qsearchs( 'part_pkg_taxproduct', - { 'taxproductnum' => $self->taxproductnum } - ); + my $part_pkg_taxproduct = $self->taxproduct(@_); $part_pkg_taxproduct ? $part_pkg_taxproduct->description : ''; } -=item part_pkg_taxrate DATA_PROVIDER, GEOCODE +=item part_pkg_taxrate DATA_PROVIDER, GEOCODE, [ CLASS ] Returns the package to taxrate m2m records for this package in the location -specified by GEOCODE (see L<FS::part_pkg_taxrate> and ). +specified by GEOCODE (see L<FS::part_pkg_taxrate>) and usage class CLASS. +CLASS may be one of 'setup', 'recur', or one of the usage classes numbers +(see L<FS::usage_class>). =cut sub _expand_cch_taxproductnum { my $self = shift; - my $part_pkg_taxproduct = - qsearchs( 'part_pkg_taxproduct', - { 'taxproductnum' => $self->taxproductnum } - ); + my $class = shift; + my $part_pkg_taxproduct = $self->taxproduct($class); + my ($a,$b,$c,$d) = ( $part_pkg_taxproduct ? ( split ':', $part_pkg_taxproduct->taxproduct ) : () @@ -859,7 +922,7 @@ sub _expand_cch_taxproductnum { sub part_pkg_taxrate { my $self = shift; - my ($data_vendor, $geocode) = @_; + my ($data_vendor, $geocode, $class) = @_; my $dbh = dbh; my $extra_sql = 'WHERE part_pkg_taxproduct.data_vendor = '. @@ -873,7 +936,9 @@ sub part_pkg_taxrate { ')'; # much more CCH oddness in m2m -- this is kludgy $extra_sql .= ' AND ('. - join(' OR ', map{ "taxproductnum = $_" } $self->_expand_cch_taxproductnum). + join(' OR ', map{ "taxproductnum = $_" } + $self->_expand_cch_taxproductnum($class) + ). ')'; my $addl_from = 'LEFT JOIN part_pkg_taxproduct USING ( taxproductnum )'; diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm index 991c33cf6..10257056d 100644 --- a/FS/FS/part_pkg/voip_cdr.pm +++ b/FS/FS/part_pkg/voip_cdr.pm @@ -187,6 +187,7 @@ sub calc_usage { my( $rate_region, $regionnum ); my $pretty_destnum; my $charge = ''; + my $classnum = ''; my @call_details = (); if ( $self->option('rating_method') eq 'prefix' || ! $self->option('rating_method') @@ -387,15 +388,18 @@ sub calc_usage { $rate_region->regionname, ); + $classnum = $rate_detail->classnum; + } if ( $charge > 0 ) { + #just use FS::cust_bill_pkg_detail objects? my $call_details; if ( $self->option('rating_method') eq 'upstream_simple' ) { - $call_details = [ 'C', $call_details[0] ]; + $call_details = [ 'C', $call_details[0], $charge, $classnum ]; }else{ $csv->combine(@call_details); - $call_details = [ 'C', $csv->string ]; + $call_details = [ 'C', $csv->string, $charge, $classnum ]; } warn " adding details on charge to invoice: [ ". join(', ', @{$call_details} ). " ]" diff --git a/httemplate/browse/part_pkg_taxproduct.cgi b/httemplate/browse/part_pkg_taxproduct.cgi index 3df819715..0cea4f838 100755 --- a/httemplate/browse/part_pkg_taxproduct.cgi +++ b/httemplate/browse/part_pkg_taxproduct.cgi @@ -54,6 +54,13 @@ my $tax_customer = $1 my $id = $1 if ( $cgi->param('id') =~ /^([ \w]+)$/ ); +my $remove_onclick = <<EOS + parent.document.getElementById('$id').value = ''; + parent.document.getElementById('${id}_description').value = ''; + parent.cClick(); +EOS + if $id; + my $select_onclick = sub { my $row = shift; my $taxnum = $row->taxproductnum; @@ -132,7 +139,10 @@ my @fields = ( my $html_init = ''; -$html_init = '<TABLE><TR><TD>Current tax product: </TD><TD>'. +my $select_link = [ 'javascript:void(0);', sub { ''; } ]; +$html_init = '<TABLE><TR><TD><A HREF="javascript:void(0)" '. + qq!onClick="$remove_onclick">(remove)</A> !. + 'Current tax product: </TD><TD>'. $selected_part_pkg_taxproduct->description. '</TD></TR></TABLE><BR><BR>' if $selected_part_pkg_taxproduct; diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index d10a09a5a..3968c8913 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -23,6 +23,7 @@ 'setuptax' => 'Setup fee tax exempt', 'recurtax' => 'Recurring fee tax exempt', 'taxclass' => 'Tax class', + 'taxproduct_select'=> 'Tax products', 'plan' => 'Price plan', 'disabled' => 'Disable new orders', 'pay_weight' => 'Payment weight', @@ -82,7 +83,21 @@ {field=>'setuptax', type=>'checkbox', value=>'Y'}, {field=>'recurtax', type=>'checkbox', value=>'Y'}, {field=>'taxclass', type=>'select-taxclass' }, - {field=>'taxproductnum', type=>'select-taxproduct' }, + { field => 'taxproductnums', + type => 'hidden', + value => join(',', @taxproductnums), + }, + { field => 'taxproduct_select', + type => 'selectlayers', + options => [ '(default)', @taxproductnums ], + curr_value => '(default)', + labels => { ( '(default)' => '(default)' ), + map {($_=>$usage_class{$_})} + @taxproductnums + }, + layer_fields => \%taxproduct_fields, + layer_values_callback => $taxproduct_values, + }, { type => 'tablebreak-tr-title', value => 'Promotions', #better name? @@ -164,16 +179,41 @@ die "access denied" #my $part_pkg = ''; my @agent_type = (); -my $tax_override; +my %tax_override = (); my $clone_part_pkg = ''; +my %taxproductnums = map { ($_->classnum => 1) } + qsearch('usage_class', { 'disabled' => '' }); + +if ( $cgi->param('error') ) { # oh well + foreach ($cgi->param) { + /^usage_taxproductnum_(\d+)$/ && ($taxproductnums{$1} = 1); + } +} elsif ( my $pkgpart = $cgi->keywords || $cgi->param('pkgpart') ) { + $pkgpart =~ /^(\d+)$/ or die "illegal pkgpart"; + my $part_pkg = qsearchs( 'part_pkg', { pkgpart => $pkgpart } ); + die "no part_pkg for pkgpart $pkgpart" unless $pkgpart; + foreach ($part_pkg->options) { + /^usage_taxproductnum_(\d+)$/ && ($taxproductnums{$1} = 1); + } + foreach ($part_pkg->part_pkg_taxoverride) { + $taxproductnums{$_->usage_class} = 1 + if $_->usage_class; + } +} else { + # do nothing +} +my @taxproductnums = ( qw( setup recur ), sort (keys %taxproductnums) ); + my %options = (); my $recur_disabled = 1; my $error_callback = sub { my($cgi, $object, $fields, $opt ) = @_; (@agent_type) = $cgi->param('agent_type'); - $tax_override = $cgi->param('tax_override'); + $tax_override{''} = $cgi->param('tax_override'); + $tax_override{$_} = $cgi->param('tax_override_$_') + foreach(grep { /^tax_override_(\w+)$/ } $cgi->param); $opt->{action} = 'Custom' if $cgi->param('clone'); $clone_part_pkg= qsearchs('part_pkg', { 'pkgpart' => $cgi->param('clone') } ); @@ -223,15 +263,19 @@ my $new_object_callback = sub { }; my $edit_callback = sub { - my( $cgi, $object, $fields ) = @_; + my( $cgi, $object, $fields, $opt ) = @_; $recur_disabled = $object->freq ? 0 : 1; (@agent_type) = map {$_->typenum} qsearch('type_pkgs',{'pkgpart'=>$1}); - $tax_override = + $tax_override{$_} = join (",", map {$_->taxclassnum} - qsearch( 'part_pkg_taxoverride', {'pkgpart' => $1} ) - ); + qsearch( 'part_pkg_taxoverride', { 'pkgpart' => $object->pkgpart, + 'usage_class' => $_, + } + ) + ) + foreach ( '', @taxproductnums ); # join (",", map {$_->taxclassnum} # $part_pkg->part_pkg_taxrate( 'cch', $conf->config('defaultloc') @@ -432,8 +476,57 @@ my $html_bottom = sub { include('/elements/selectlayers.html', %selectlayers, 'layers_only'=>1 ). '<SCRIPT TYPE="text/javascript">'. include('/elements/selectlayers.html', %selectlayers, 'js_only'=>1 ). + "taxproduct_selectchanged(document.getElementById('taxproduct_select'));". '</SCRIPT>'; }; +my %usage_class = map { ($_->classnum => $_->classname) } + qsearch('usage_class', {}); +$usage_class{setup} = 'Setup'; +$usage_class{recur} = 'Recurring'; + +my %taxproduct_fields = map { $_ => [ "taxproductnum_$_", + { type => 'select-taxproduct', + #label => "$usage_class{$_} tax product", + }, + "tax_override_$_", + { type => 'select-taxoverride' } + ] + } + @taxproductnums; +$taxproduct_fields{'(default)'} = + [ 'taxproductnum', { type => 'select-taxproduct', + #label => 'Default tax product', + }, + 'tax_override', { type => 'select-taxoverride' }, + ]; + +my $taxproduct_values = sub { + my ($cgi, $object, $flags) = @_; + my $routine = + sub { my $layer = shift; + my @fields = @{$taxproduct_fields{$layer}}; + my @values = (); + while( @fields ) { + my $field = shift @fields; + shift @fields; + $field =~ /^taxproductnum_\w+$/ && + push @values, ( $field => $options{"usage_$field"} ); + $field =~ /^tax_override_(\w+)$/ && + push @values, ( $field => $tax_override{$1} ); + $field =~ /^taxproductnum$/ && + push @values, ( $field => $object->taxproductnum ); + $field =~ /^tax_override$/ && + push @values, ( $field => $tax_override{''} ); + } + { (@values) }; + }; + + my @result = + map { ( $_ => { &{$routine}($_) } ) } ( '(default)', @taxproductnums ); + return({ @result }); + +}; + </%init> diff --git a/httemplate/edit/part_pkg_taxoverride.html b/httemplate/edit/part_pkg_taxoverride.html index ba709ce64..5f191b373 100644 --- a/httemplate/edit/part_pkg_taxoverride.html +++ b/httemplate/edit/part_pkg_taxoverride.html @@ -10,7 +10,7 @@ <TR><TD> <FORM="dummy"> <CENTER> - <INPUT type="submit" value="Finish" onclick="s=fetchSelected(); s.shift(); parent.document.getElementById('tax_override').value=s.toString(); parent.cClick();"> + <INPUT type="submit" value="Finish" onclick="s=fetchSelected(); s.shift(); parent.document.getElementById('<% $element_name || "tax_override" %>').value=s.toString(); parent.cClick();"> <INPUT type="reset" value="Cancel" onclick="parent.cClick();"> </CENTER> </FORM> @@ -121,4 +121,7 @@ my $unselected_offset = $1 my $selected = $1 if $cgi->param('selected') =~/^([,\d]+)$/; +my $element_name = $1 + if $cgi->param('element_name') =~/^(\w+)$/; + </%init> diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi index 91ae97132..d338b8299 100755 --- a/httemplate/edit/process/part_pkg.cgi +++ b/httemplate/edit/process/part_pkg.cgi @@ -84,6 +84,13 @@ my $args_callback = sub { } @options; + foreach ( split(',', $cgi->param('taxproductnums') ) ) { + my $value = $cgi->param("taxproductnum_$_"); + $error ||= "Illegal taxproductnum_$_: $value" + unless ( $value =~ /^\d*$/ ); + $options{"usage_taxproductnum_$_"} = $value; + } + $options{$_} = scalar( $cgi->param($_) ) for (qw( setup_fee recur_fee )); @@ -146,6 +153,22 @@ my @process_m2m = ( }, ); +foreach my $override_class ($cgi->param) { + next unless $override_class =~ /^tax_override_(\w+)$/; + my $class = $1; + + my (@tax_overrides) = (grep "$_", split (",", $1)) + if $cgi->param($override_class) =~ /^([\d,]+)$/; + + push @process_m2m, { + 'link_table' => 'part_pkg_taxoverride', + 'target_table' => 'tax_class', + 'hashref' => { 'usage_class' => $class }, + 'params' => \@tax_overrides, + }; + +} + my $conf = new FS::Conf; if ( $cgi->param('pkgpart') || ! $conf->exists('agent_defaultpkg') ) { diff --git a/httemplate/elements/select-taxoverride.html b/httemplate/elements/select-taxoverride.html new file mode 100644 index 000000000..f3bfb5fd7 --- /dev/null +++ b/httemplate/elements/select-taxoverride.html @@ -0,0 +1,26 @@ + <INPUT NAME = "<% $name %>" + ID = "<% $name %>" + TYPE = "hidden" + VALUE = "<% $value %>" + > + <A href="javascript:void(0)" onclick="overlib( OLiframeContent('part_pkg_taxoverride.html?element_name=<% $name %>;selected='+document.getElementById('<% $name %>').value, 1100, 600, 'tax_product_popup'), CAPTION, 'Edit <% $class %> product tax overrides', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK); return false;"> + <% $value ? "Edit $class tax overrides" : "Override $class taxes" %> + </A> +<%init> + +my %opt = @_; +my $name = $opt{element_name} || $opt{field} || 'tax_override'; +my $value = length($opt{curr_value}) ? $opt{curr_value} : $opt{value}; + +my %usage_class = map { ($_->classnum => $_->classname) } + qsearch('usage_class', {}); +$usage_class{setup} = 'Setup'; +$usage_class{recur} = 'Recurring'; + +my $usage; +$name =~ /^tax_override_(\w+)$/ && ( $usage = $1 ); + +my $class = lc($usage_class{$usage} || "Usage class $usage") + if $usage; + +</%init> diff --git a/httemplate/elements/select-taxproduct.html b/httemplate/elements/select-taxproduct.html index 74044b108..37a428960 100644 --- a/httemplate/elements/select-taxproduct.html +++ b/httemplate/elements/select-taxproduct.html @@ -1,28 +1,26 @@ -<% ntable("#cccccc", 2) %> - <TR> - <TD align="right">Tax product</TD> - <TD> - <INPUT name="taxproductnum" id="taxproductnum" type="hidden" value="<% $opt{'taxproductnum'}%>"> - <INPUT name="taxproductnum_description" id="taxproductnum_description" type="text" value="<% $opt{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="<% $opt{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;"> - <% $opt{tax_override} ? 'Edit tax overrides' : 'Override taxes' %> - </A> - </TD> - </TR> -</TABLE> - +<% $opt{'prefix'} %><INPUT NAME = "<% $name %>" + ID = "<% $name %>" + TYPE = "hidden" + VALUE = "<% $value |h %>" + > + <INPUT NAME = "<% $name %>_description" + ID = "<% $name %>_description" + TYPE = "text" + VALUE = "<% $description %>" + SIZE = "12" + onclick = "overlib( OLiframeContent('<% $p %>/browse/part_pkg_taxproduct.cgi?_type=select&id=<% $name %>&taxproductnum='+document.getElementById('<% $name %>').value, 1000, 400, 'tax_product_popup'), CAPTION, 'Select product', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK); return false;"><% $opt{'postfix'} %> <%init> my %opt = @_; - -$opt{'taxproductnum'} ||= $opt{'object'}->taxproductnum - if $opt{'object'}; -$opt{'taxproduct_description'} ||= $opt{'object'}->taxproduct_description - if $opt{'object'}; +my $name = $opt{element_name} || $opt{field} || 'taxproductnum'; +my $value = length($opt{curr_value}) ? $opt{curr_value} : $opt{value}; +my $description = $opt{'taxproduct_description'}; + +unless ( $description || ! $value ) { + my $part_pkg_taxproduct = + qsearchs( 'part_pkg_taxproduct', { 'taxproductnum'=> $value } ); + $description = $part_pkg_taxproduct->description + if $part_pkg_taxproduct; +} </%init> diff --git a/httemplate/elements/tr-select-taxoverride.html b/httemplate/elements/tr-select-taxoverride.html new file mode 100644 index 000000000..4954a5566 --- /dev/null +++ b/httemplate/elements/tr-select-taxoverride.html @@ -0,0 +1,18 @@ +% if ( $conf->exists('enable_taxproducts') ) { + <%include('tr-td-label.html', @_) %> + <TD <% $cell_style %>><% include('select-taxoverride.html', @_) %></TD> + </TR> + +% } else { + <INPUT TYPE="hidden" NAME="<% $name %>" VALUE="<% $opt{value} %>"> +% } + +<%init> + +my $conf = new FS::conf; + +my %opt = @_; +my $cell_style = $opt{'cell_style'}? 'STYLE="'. $opt{cell_style}. '"' : ''; +my $name = $opt{name} || 'tax_override'; + +</%init> diff --git a/httemplate/elements/tr-select-taxproduct.html b/httemplate/elements/tr-select-taxproduct.html index f9f192a28..951222475 100644 --- a/httemplate/elements/tr-select-taxproduct.html +++ b/httemplate/elements/tr-select-taxproduct.html @@ -1,22 +1,18 @@ % if ( $conf->exists('enable_taxproducts') ) { - - <TR> - <TD COLSPAN="2"> - <% include('select-taxproduct.html', @_) %> - </TD> - </TR> + <%include('tr-td-label.html', @_) %> + <TD <% $cell_style %>><% include('select-taxproduct.html', @_) %></TD> + </TR> % } else { - - <INPUT TYPE="hidden" NAME="taxproductnum" VALUE="<% $opt{taxproductnum} %>"> - <INPUT TYPE="hidden" NAME="tax_override" VALUE="<% $opt{tax_override} %>"> - + <INPUT TYPE="hidden" NAME="<% $name %>" VALUE="<% $opt{value} %>"> % } <%init> -my $conf = new FS::Conf; +my $conf = new FS::conf; my %opt = @_; +my $cell_style = $opt{cell_style} ? 'STYLE="'. $opt{cell_style}. '"' : ''; +my $name = $opt{name} || 'taxproductnum'; </%init> |