diff options
| -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>  | 
