credits return taxes, but the magic calculation button does not yet work properly...
authorjeff <jeff>
Mon, 26 Oct 2009 07:12:12 +0000 (07:12 +0000)
committerjeff <jeff>
Mon, 26 Oct 2009 07:12:12 +0000 (07:12 +0000)
16 files changed:
FS/FS/Conf.pm
FS/FS/Schema.pm
FS/FS/cust_bill_ApplicationCommon.pm
FS/FS/cust_bill_pay_pkg.pm
FS/FS/cust_bill_pkg.pm
FS/FS/cust_bill_pkg_tax_location.pm
FS/FS/cust_bill_pkg_tax_rate_location.pm
FS/FS/cust_credit_bill_pkg.pm
httemplate/edit/cust_credit.cgi
httemplate/edit/elements/ApplicationCommon.html
httemplate/edit/process/elements/ApplicationCommon.html
httemplate/search/cust_bill_pkg.cgi
httemplate/search/report_newtax.cgi
httemplate/search/report_tax.cgi
httemplate/view/cust_main/payment_history/credit.html
httemplate/view/cust_main/payment_history/payment.html

index 56dae58..a57c473 100644 (file)
@@ -3223,6 +3223,20 @@ worry that config_items is freeside-specific and icky.
     'type'        => 'checkbox',
   },
 
+  {
+    'key'         => 'cust_bill_pay_pkg-manual',
+    'section'     => 'UI',
+    'description' => 'Allow manual application of payments to line items.',
+    'type'        => 'checkbox',
+  },
+
+  {
+    'key'         => 'cust_credit_bill_pkg-manual',
+    'section'     => 'UI',
+    'description' => 'Allow manual application of credits to line items.',
+    'type'        => 'checkbox',
+  },
+
   { key => "apacheroot", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
   { key => "apachemachine", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
   { key => "apachemachines", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" },
index dfa3328..2b0ea90 100644 (file)
@@ -664,6 +664,8 @@ sub tables_hashref {
         'creditbillpkgnum', 'serial', '',      '', '', '',
         'creditbillnum',       'int', '',      '', '', '',
         'billpkgnum',          'int', '',      '', '', '',
+        'billpkgtaxlocationnum', 'int', 'NULL', '', '', '',
+        'billpkgtaxratelocationnum', 'int', 'NULL', '', '', '',
         'amount',            @money_type,          '', '',
         'setuprecur',      'varchar', '', $char_d, '', '',
         'sdate',   @date_type, '', '', 
@@ -671,7 +673,11 @@ sub tables_hashref {
       ],
       'primary_key' => 'creditbillpkgnum',
       'unique'      => [],
-      'index'       => [ [ 'creditbillnum' ], [ 'billpkgnum' ], ],
+      'index'       => [ [ 'creditbillnum' ],
+                         [ 'billpkgnum' ], 
+                         [ 'billpkgtaxlocationnum' ],
+                         [ 'billpkgtaxratelocationnum' ],
+                       ],
     },
 
     'cust_main' => {
@@ -1076,6 +1082,8 @@ sub tables_hashref {
         'billpaypkgnum', 'serial', '', '', '', '',
         'billpaynum',       'int', '', '', '', '',
         'billpkgnum',       'int', '', '', '', '',
+        'billpkgtaxlocationnum', 'int', 'NULL', '', '', '',
+        'billpkgtaxratelocationnum', 'int', 'NULL', '', '', '',
         'amount',         @money_type,     '', '',
         'setuprecur',      'varchar', '', $char_d, '', '',
        'sdate',   @date_type, '', '', 
index 7449679..7f564cd 100644 (file)
@@ -112,8 +112,7 @@ Auto-applies this invoice application to specific line items, if possible.
 
 =cut
 
-sub apply_to_lineitems {
-  #my $self = shift;
+sub calculate_applications {
   my( $self, %options ) = @_;
 
   return '' if $skip_apply_to_lineitems_hack;
@@ -122,29 +121,43 @@ sub apply_to_lineitems {
 
   my $conf = new FS::Conf;
 
-  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 @open = $self->cust_bill->open_cust_bill_pkg; #FOR UPDATE...?
 
-  my $oldAutoCommit = $FS::UID::AutoCommit;
-  local $FS::UID::AutoCommit = 0;
-  my $dbh = dbh;
+  if ( exists($options{subitems}) ) {
+    my $i = 0;
+    my %open = ();
+    $open{$_->billpkgnum} = $i++ foreach @open;
+
+    foreach my $listref ( @{$options{subitems}} ) {
+      my ($billpkgnum, $itemamount, $taxlocationnum) = @$listref;
+      return "Can't apply a ". $self->_app_source_name. ' of $'. $listref->[1].
+             " to line item $billpkgnum which is not open"
+        unless exists($open{$billpkgnum});
+      my $itemindex = $open{$billpkgnum};
+      my %taxhash = ();
+      if ($taxlocationnum) {
+        %taxhash = map { ($_->primary_key => $_->get($_->primary_key)) }
+                   grep { $_->get($_->primary_key) == $taxlocationnum }
+                   $open[$itemindex]->cust_bill_pkg_tax_Xlocation;
+
+        return "No tax line item with a key value of $taxlocationnum exists"
+          unless scalar(%taxhash);
+      }
+      push @apply, [ $open[$itemindex], $itemamount, { %taxhash } ];
+    }
+    return \@apply;
+  }
 
-  my @open = $self->cust_bill->open_cust_bill_pkg; #FOR UPDATE...?
   @open = grep { $_->pkgnum == $self->pkgnum } @open
     if $conf->exists('pkg-balances') && $self->pkgnum;
   warn "$me ". scalar(@open). " open line items for invoice ".
        $self->cust_bill->invnum. ": ". join(', ', @open). "\n"
     if $DEBUG;
   my $total = 0;
-  $total += $_->setup + $_->recur foreach @open;
+  $total += $_->owed_setup + $_->owed_recur foreach @open;
   $total = sprintf('%.2f', $total);
 
   if ( $self->amount > $total ) {
-    $dbh->rollback if $oldAutoCommit;
     return "Can't apply a ". $self->_app_source_name. ' of $'. $self->amount.
            " greater than the remaining owed on line items (\$$total)";
   }
@@ -159,7 +172,7 @@ sub apply_to_lineitems {
       if $DEBUG;
 
     #@apply = map { [ $_, $_->amount ]; } @open;
-    @apply = map { [ $_, $_->setup || $_->recur ]; } @open;
+    @apply = map { [ $_, $_->owed_setup + 0 || $_->owed_recur + 0 ]; } @open;
 
   } else {
 
@@ -167,8 +180,8 @@ sub apply_to_lineitems {
     # - amount exactly and uniquely matches a single open lineitem
     #   (you must be trying to pay or credit that item, then)
 
-    my @same = grep {    $_->setup == $self->amount
-                      || $_->recur == $self->amount
+    my @same = grep {    $_->owed_setup == $self->amount
+                      || $_->owed_recur == $self->amount
                     }
                     @open;
     if ( scalar(@same) == 1 ) {
@@ -213,7 +226,7 @@ sub apply_to_lineitems {
       my @items = map { $_->[0] } grep { $weight == $_->[1] } @openweight;
 
       my $itemtotal = 0;
-      foreach my $item (@items) { $itemtotal += $item->setup || $item->recur; }
+      foreach my $item (@items) { $itemtotal += $item->owed_setup + 0 || $item->owed_recur + 0; }
       my $applytotal = min( $itemtotal, $remaining_amount );
       $remaining_amount -= $applytotal;
 
@@ -234,7 +247,7 @@ sub apply_to_lineitems {
 
        my @newitems = ();
        foreach my $item ( @items ) {
-         my $itemamount = $item->setup || $item->recur;
+         my $itemamount = $item->owed_setup + 0 || $item->owed_recur + 0;
           if ( $itemamount < $applyeach ) {
            warn "$me applying full $itemamount".
                 " to small line item (cust_bill_pkg ". $item->billpkgnum. ")\n"
@@ -265,7 +278,6 @@ sub apply_to_lineitems {
       if ( abs($diff) > scalar(@items) ) {
         #we must have done something really wrong, the difference is more than
        #a penny an item
-       $dbh->rollback if $oldAutoCommit;
        return 'Error distributing pennies applying '. $self->_app_source_name.
               " - can't distribute difference of $diff pennies".
               ' among '. scalar(@items). ' line items';
@@ -288,7 +300,6 @@ sub apply_to_lineitems {
       }
 
       if ( sprintf('%.0f', $diff ) ) {
-        $dbh->rollback if $oldAutoCommit;
        return "couldn't futz with pennies enough: still $diff left";
       }
 
@@ -308,12 +319,69 @@ sub apply_to_lineitems {
 
   }
 
+  # break down lineitem amounts for tax lines
+  # could expand @open above, instead, for a slightly different magic effect
+  my @result = ();
+  foreach my $apply ( @apply ) {
+    my @sub_lines = $apply->[0]->cust_bill_pkg_tax_Xlocation;
+    my $amount = $apply->[1];
+    warn "applying ". $apply->[1]. " to ". $apply->[0]->desc
+      if $DEBUG;
+    
+    foreach my $subline ( @sub_lines ) {
+      my $owed = $subline->owed;
+      push @result, [ $apply->[0],
+                      sprintf('%.2f', min($amount, $owed) ),
+                      { $subline->primary_key => $subline->get($subline->primary_key) },
+                    ];
+      $amount -= $owed;
+      $amount = 0 if $amount < 0;
+      last unless $amount;
+    }
+    if ( $amount > 0 ) {
+      push @result, [ $apply->[0], sprintf('%.2f', $amount), {} ];
+    }
+  }
+
+  \@result;
+
+}
+
+sub apply_to_lineitems {
+  #my $self = shift;
+  my( $self, %options ) = @_;
+
+  return '' if $skip_apply_to_lineitems_hack;
+
+
+
+  my $conf = new FS::Conf;
+
+  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;
+
+  my $listref_or_error = $self->calculate_applications(%options);
+  unless (ref($listref_or_error)) {
+    $dbh->rollback if $oldAutoCommit;
+    return $listref_or_error;
+  }
+
+  my @apply = @$listref_or_error;
+
   # do the applicaiton(s)
   my $table = $self->lineitem_breakdown_table;
   my $source_key = dbdef->table($self->table)->primary_key;
   my $applied = 0;
   foreach my $apply ( @apply ) {
-    my ( $cust_bill_pkg, $amount ) = @$apply;
+    my ( $cust_bill_pkg, $amount, $taxcreditref ) = @$apply;
     $applied += $amount;
     my $application = "FS::$table"->new( {
       $source_key  => $self->$source_key(),
@@ -322,6 +390,7 @@ sub apply_to_lineitems {
       'setuprecur' => ( $cust_bill_pkg->setup > 0 ? 'setup' : 'recur' ),
       'sdate'      => $cust_bill_pkg->sdate,
       'edate'      => $cust_bill_pkg->edate,
+      %$taxcreditref,
     });
     my $error = $application->insert(%options);
     if ( $error ) {
index 639960f..eb2e80c 100644 (file)
@@ -149,6 +149,13 @@ sub check {
     $self->ut_numbern('billpaypkgnum')
     || $self->ut_foreign_key('billpaynum', 'cust_bill_pay', 'billpaynum' )
     || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
+    || $self->ut_foreign_keyn('pkgnum', 'cust_pkg', 'pkgnum')
+    || $self->ut_foreign_keyn('billpkgtaxlocationnum',
+                              'cust_bill_pkg_tax_location',
+                              'billpkgtaxlocationnum')
+    || $self->ut_foreign_keyn('billpkgtaxratelocationnum',
+                              'cust_bill_pkg_tax_rate_location',
+                              'billpkgtaxratelocationnum')
     || $self->ut_money('amount')
     || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] )
     || $self->ut_numbern('sdate')
index 2d32d31..4058f1f 100644 (file)
@@ -780,6 +780,24 @@ sub _cust_tax_exempt_pkg {
 
 }
 
+=item cust_bill_pkg_tax_Xlocation
+
+Returns the list of associated cust_bill_pkg_tax_location and/or
+cust_bill_pkg_tax_rate_location objects
+
+=cut
+
+sub cust_bill_pkg_tax_Xlocation {
+  my $self = shift;
+
+  my %hash = ( 'billpkgnum' => $self->billpkgnum );
+
+  (
+    qsearch ( 'cust_bill_pkg_tax_location', { %hash  } ),
+    qsearch ( 'cust_bill_pkg_tax_rate_location', { %hash } )
+  );
+
+}
 
 =back
 
index db65237..0d3bd3a 100644 (file)
@@ -6,6 +6,8 @@ use FS::Record qw( qsearch qsearchs );
 use FS::cust_bill_pkg;
 use FS::cust_pkg;
 use FS::cust_location;
+use FS::cust_bill_pay_pkg;
+use FS::cust_credit_bill_pkg;
 
 =head1 NAME
 
@@ -122,6 +124,78 @@ sub check {
   $self->SUPER::check;
 }
 
+=item cust_bill_pkg
+
+Returns the associated cust_bill_pkg object
+
+=cut
+
+sub cust_bill_pkg {
+  my $self = shift;
+  qsearchs( 'cust_bill_pkg', { 'billpkgnum' => $self->billpkgnum }  );
+}
+
+=item cust_location
+
+Returns the associated cust_location object
+
+=cut
+
+sub cust_location {
+  my $self = shift;
+  qsearchs( 'cust_location', { 'locationnum' => $self->locationnum }  );
+}
+
+=item desc
+
+Returns a description for this tax line item constituent.  Currently this
+is the desc of the associated line item followed by the state/county/city
+for the location in parentheses.
+
+=cut
+
+sub desc {
+  my $self = shift;
+  my $cust_location = $self->cust_location;
+  my $location = join('/', grep { $_ }                 # leave in?
+                           map { $cust_location->$_ }
+                           qw( state county city )     # country?
+  );
+  $self->cust_bill_pkg->desc. " ($location)";
+}
+
+=item owed
+
+Returns the amount owed (still outstanding) on this tax line item which is
+the amount of this record minus all payment applications and credit
+applications.
+
+=cut
+
+sub owed {
+  my $self = shift;
+  my $balance = $self->amount;
+  $balance -= $_->amount foreach ( $self->cust_bill_pay_pkg('setup') );
+  $balance -= $_->amount foreach ( $self->cust_credit_bill_pkg('setup') );
+  $balance = sprintf( '%.2f', $balance );
+  $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
+  $balance;
+}
+
+sub cust_bill_pay_pkg {
+  my $self = shift;
+  qsearch( 'cust_bill_pay_pkg',
+           { map { $_ => $self->$_ } qw( billpkgtaxlocationnum billpkgnum ) }
+         );
+}
+
+sub cust_credit_bill_pkg {
+  my $self = shift;
+  qsearch( 'cust_credit_bill_pkg',
+           { map { $_ => $self->$_ } qw( billpkgtaxlocationnum billpkgnum ) }
+         );
+}
+
 =back
 
 =head1 BUGS
index fc5734f..89c2529 100644 (file)
@@ -5,7 +5,9 @@ use base qw( FS::Record );
 use FS::Record qw( qsearch qsearchs );
 use FS::cust_bill_pkg;
 use FS::cust_pkg;
-use FS::cust_location;
+use FS::tax_rate_location;
+use FS::cust_bill_pay_pkg;
+use FS::cust_credit_bill_pkg;
 
 =head1 NAME
 
@@ -122,6 +124,85 @@ sub check {
   $self->SUPER::check;
 }
 
+=item cust_bill_pkg
+
+Returns the associated cust_bill_pkg object
+
+=cut
+
+sub cust_bill_pkg {
+  my $self = shift;
+  qsearchs( 'cust_bill_pkg', { 'billpkgnum' => $self->billpkgnum }  );
+}
+
+=item tax_rate_location
+
+Returns the associated tax_rate_location object
+
+=cut
+
+sub tax_rate_location {
+  my $self = shift;
+  qsearchs( 'tax_rate_location',
+            { 'taxratelocationnum' => $self->taxratelocationnum }
+  );
+}
+
+=item desc
+
+Returns a description for this tax line item constituent.  Currently this
+is the desc of the associated line item followed by the
+state,county,city,locationtaxid for the location in parentheses.
+
+=cut
+
+sub desc {
+  my $self = shift;
+  my $tax_rate_location = $self->tax_rate_location;
+  my $location = join(', ', grep { $_ }
+                            map { $tax_rate_location->$_ }
+                            qw( state county city )
+  );
+  $location .= ( $location && $self->locationtaxid ) ? ', ' : '';
+  $location .= $self->locationtaxid;
+  $self->cust_bill_pkg->desc. " ($location)";
+}
+
+
+=item owed
+
+Returns the amount owed (still outstanding) on this tax line item which is 
+the amount of this record minus all payment applications and credit
+applications.
+
+=cut
+
+sub owed {
+  my $self = shift;
+  my $balance = $self->amount;
+  $balance -= $_->amount foreach ( $self->cust_bill_pay_pkg('setup') );
+  $balance -= $_->amount foreach ( $self->cust_credit_bill_pkg('setup') );
+  $balance = sprintf( '%.2f', $balance );
+  $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
+  $balance;
+}
+
+sub cust_bill_pay_pkg {
+  my $self = shift;
+  qsearch( 'cust_bill_pay_pkg', { map { $_ => $self->$_ }
+                                  qw( billpkgtaxratelocationnum billpkgnum )
+                                }
+         );
+}
+
+sub cust_credit_bill_pkg {
+  my $self = shift;
+  qsearch( 'cust_credit_bill_pkg', { map { $_ => $self->$_ }
+                                     qw( billpkgtaxratelocationnum billpkgnum )
+                                   }
+         );
+}
+
 =back
 
 =head1 BUGS
index 7252be5..543a71f 100644 (file)
@@ -114,6 +114,12 @@ sub check {
     $self->ut_numbern('creditbillpkgnum')
     || $self->ut_foreign_key('creditbillnum', 'cust_credit_bill', 'creditbillnum')
     || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
+    || $self->ut_foreign_keyn('billpkgtaxlocationnum',
+                              'cust_bill_pkg_tax_location',
+                              'billpkgtaxlocationnum')
+    || $self->ut_foreign_keyn('billpkgtaxratelocationnum',
+                              'cust_bill_pkg_tax_rate_location',
+                              'billpkgtaxratelocationnum')
     || $self->ut_money('amount')
     || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] )
     || $self->ut_numbern('sdate')
index 5785a05..1caec3d 100755 (executable)
@@ -40,6 +40,11 @@ Credit
     <TD><SELECT NAME="apply"><OPTION VALUE="yes" SELECTED>yes<OPTION>no</SELECT></TD>
   </TR>
 
+  <TR>
+    <TD ALIGN="right">Tax</TD>
+    <TD><SELECT NAME="tax"><OPTION VALUE="included" SELECTED>is included<OPTION VALUE="calculate">is to be calculated</SELECT></TD>
+  </TR>
+
 % if ( $conf->exists('pkg-balances') ) {
   <% include('/elements/tr-select-cust_pkg-balances.html',
                'custnum' => $custnum,
index a485d37..b46a3c8 100644 (file)
@@ -90,16 +90,71 @@ function changed(what) {
 
     if ( dst == <% $dst->$dst_pkey %> ) {
       what.form.amount.value = "<% min($dst->$dst_unapplied, $unapplied) %>";
+%     if ($use_sub_dst_thing) {
+        what.form.display_amount.value = "<% min($dst->$dst_unapplied, $unapplied) %>";
+
+        var rownum=0
+        var table = document.getElementById('ApplicationTable');
+        while(table.rows[2]) {
+          table.deleteRow(2);
+        }
+%       my $app_class = "FS::$link_table";
+%       my $temp_app = $app_class->new(
+%         { $src_pkey => $src_pkeyvalue,
+%           $dst_pkey => $dst->$dst_pkey,
+%           'amount'  => min($dst->$dst_unapplied, $unapplied),
+%         }
+%       );
+%       my %apphash = ();
+%       my $listref_or_error = $temp_app->calculate_applications;
+%       %apphash = map { &{$key_generator}($_), $_ } @$listref_or_error
+%         if ref($listref_or_error);
+%       foreach my $cbp ( $dst->open_cust_bill_pkg ) {
+%         my $desc = $cbp->desc;
+%         my $total_owed = $cbp->owed_setup + $cbp->owed_recur;
+%         my $key = &{$key_generator}([ $cbp, 0, {} ]);
+%         my $amount = exists($apphash{ $key }) ? $apphash{ $key }->[1] : 0;
+%         unless ( $cbp->pkgnum ) {
+%           foreach my $taxX ( $cbp->cust_bill_pkg_tax_Xlocation ) {
+%             my $pkey = $taxX->primary_key;
+%             my $owed = $taxX->owed;
+%             my $key = &{$key_generator}([ $cbp, 0, { $pkey => $taxX->$pkey } ]);
+%             my $toapp = exists($apphash{ $key }) ? $apphash{ $key }->[1] : 0;
+              <% &{$row_generator}( $cbp, $taxX->desc, $owed, $toapp, $taxX->$pkey ) %>
+%             $total_owed -= $owed;
+%             $amount -= $toapp;
+%           }
+%           $desc .= ' (default)';
+%         }
+%         if ( $total_owed > 0 ) {
+            <% &{$row_generator}($cbp, $desc, $total_owed, $amount, '') %>
+%         }
+%       }
+%     }
     }
 
 % } 
 
 }
+
+function sub_changed(what) {
+
+  var amount = 0;
+  var table = document.getElementById('ApplicationTable');
+  var i = table.rows.length;
+  while(i-- > 2) {
+    var amount_input = table.rows[i].getElementsByTagName('input').item(0);
+    amount += parseFloat( amount_input.value ) || 0; 
+  }
+  what.form.amount.value = parseFloat(amount).toFixed(2);
+  what.form.display_amount.value = parseFloat(amount).toFixed(2);
+
+}
 </SCRIPT>
 
 Apply to:
 
-<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+<TABLE ID="ApplicationTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
 
 <TR>
   <TD ALIGN="right"><% $dst_thing %>: </TD>
@@ -116,7 +171,10 @@ Apply to:
 
 <TR>
   <TD ALIGN="right">Amount: </TD>
-  <TD><% $money_char %><INPUT TYPE="text" NAME="amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8></TD>
+  <TD><% $money_char %><INPUT TYPE="text" NAME="<% $use_sub_dst_thing ? 'display_' : '' %>amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8 <% $use_sub_dst_thing ? 'DISABLED' : '' %> STYLE="text-align:right;"></TD>
+% if ($use_sub_dst_thing) {
+  <INPUT TYPE="hidden" NAME="amount" VALUE="<% $amount %>" >
+% }
 </TR>
 
 </TABLE>
@@ -144,6 +202,13 @@ my $dst_table = $opt{'dst_table'};
 my $dst_pkey = dbdef->table($dst_table)->primary_key;
 my $dst_unapplied = $dst_table eq 'cust_bill' ? 'owed' : 'unapplied';
 
+$opt{form_action} =~ /^process\/(.*)\./ or die "bad form action";
+my $link_table = $1;
+
+my $use_sub_dst_thing = 0;
+$use_sub_dst_thing = 1
+  if ( $dst_table eq 'cust_bill' && $conf->exists("${link_table}_pkg-manual") );
+
 my $to = $dst_table eq 'cust_refund' ? ' to Refund' : '';
 
 my($src_pkeyvalue, $amount, $dst_pkeyvalue);
@@ -174,4 +239,59 @@ my @dst = sort {    $a->_date     <=> $b->_date
           grep { $_->$dst_unapplied != 0 }
           qsearch($dst_table, { 'custnum' => $src->custnum } );
 
+my $row_generator = sub {
+  my ($cust_bill_pkg, $desc, $owed, $amount, $taxXnum) = @_;
+  my $id = $cust_bill_pkg->pkgnum || 'Tax';
+  my $billpkgnum = $cust_bill_pkg->billpkgnum;
+
+  $amount = sprintf("%.2f", $amount);
+  qq!
+      var tablebody = document.getElementsByTagName('tbody').item(0);
+      var row = table.insertRow(rownum+2);
+      var pkg_cell = document.createElement('TD');
+      pkg_cell.style.textAlign = 'right';
+      pkg_cell.innerHTML = "$id - $desc - $owed:";
+      var amount_cell = document.createElement('TD');
+      amount_cell.innerHTML = "$money_char";
+      var amount_input = document.createElement('INPUT');
+      amount_input.setAttribute('name', 'subamount'+rownum);
+      amount_input.setAttribute('id',   'subamount'+rownum);
+      amount_input.style.textAlign = 'right';
+      amount_input.setAttribute('size', 8);
+      amount_input.setAttribute('maxlength', 8);
+      amount_input.setAttribute('rownum', rownum);
+      amount_input.setAttribute('value', "$amount");
+      amount_input.setAttribute('onChange', "sub_changed(this);");
+      amount_cell.appendChild(amount_input);
+      var subnum_input = document.createElement('INPUT');
+      subnum_input.setAttribute('name', 'subnum'+rownum);
+      subnum_input.setAttribute('id',   'subnum'+rownum);
+      subnum_input.setAttribute('type', 'hidden');
+      subnum_input.setAttribute('rownum', rownum);
+      subnum_input.setAttribute('value', "$billpkgnum");
+      amount_cell.appendChild(subnum_input);
+      var taxnum_input = document.createElement('INPUT');
+      taxnum_input.setAttribute('name', 'taxXlocationnum'+rownum);
+      taxnum_input.setAttribute('id',   'taxXlocationnum'+rownum);
+      taxnum_input.setAttribute('type', 'hidden');
+      taxnum_input.setAttribute('rownum', rownum);
+      taxnum_input.setAttribute('value', "$taxXnum");
+      amount_cell.appendChild(taxnum_input);
+      row.appendChild(pkg_cell);
+      row.appendChild(amount_cell);
+      rownum++;
+    !;
+};
+
+my $key_generator = sub {
+  my $listref = shift;
+  my ($cust_bill_pkg, $amount, $hashref) = @$listref;
+  my $setup_or_recur = $cust_bill_pkg->setup > 0 ? 'setup' : 'recur';
+  my $taxlinenum = $hashref->{billpkgtaxlocationnum} ||
+                   $hashref->{billpkgtaxratelocationnum} ||
+                   '';
+                   
+  join(':', $cust_bill_pkg->billpkgnum, $setup_or_recur, $taxlinenum);
+};
+
 </%init>
index e0c5bd7..3cb7ae6 100644 (file)
@@ -51,6 +51,14 @@ my $cust_main = qsearchs('cust_main', { 'custnum' => $src->custnum } )
 
 my $custnum = $cust_main->custnum;
 
+my @subnames = grep { /.+/ } map { /^subnum(\d+)$/ ? $1 : '' } $cgi->param;
+my @subitems = map { [ $cgi->param("subnum$_"), $cgi->param("subamount$_"), $cgi->param("taxXlocationnum$_") ] }
+               @subnames;
+{ local $^W = 0; @subitems = grep { $_->[1] + 0 } @subitems; }
+
+my %options = ();
+$options{subitems} = \@subitems if scalar(@subitems);
 my $new;
 #  $new = new FS::cust_refund ( {
 #    'reason'  => 'Refunding payment', #enter reason in UI
@@ -72,6 +80,8 @@ my $new;
 
 #}
 
-my $error = $new->insert( 'manual' => 1 );
+
+$options{manual} = 1;
+my $error = $new->insert( %options );
 
 </%init>
index 52f59de..975a307 100644 (file)
@@ -404,8 +404,6 @@ if ( $cgi->param('pkg_tax') ) {
 
 }
 
-my $where = ' WHERE '. join(' AND ', @where);
-
 my $join_cust =  '      JOIN cust_bill USING ( invnum ) 
                    LEFT JOIN cust_main USING ( custnum ) ';
 
@@ -427,14 +425,28 @@ if ( $cgi->param('nottax') ) {
     $join_pkg .= ' LEFT JOIN cust_bill_pkg_tax_location USING ( billpkgnum )
                    LEFT JOIN cust_location              USING ( locationnum ) ';
 
-    #quelle kludge, false laziness w/report_tax.cgi
-    $where =~ s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g; 
-  } elsif ( scalar( grep( /locationtaxid/, $cgi->param ) ) ) {
+    #quelle kludge, somewhat false laziness w/report_tax.cgi
+    s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g for @where;
+  } elsif ( scalar( grep( /locationtaxid/, $cgi->param ) ) ||
+            $cgi->param('iscredit') eq 'rate') {
     $join_pkg .=
       ' LEFT JOIN cust_bill_pkg_tax_rate_location USING ( billpkgnum ) '.
       ' LEFT JOIN tax_rate_location USING ( taxratelocationnum ) ';
   }
 
+  if ( $cgi->param('iscredit') ) {
+    $join_pkg .= ' JOIN cust_credit_bill_pkg USING ( billpkgnum';
+    if ( $conf->exists('tax-pkg_address') ) {
+      $join_pkg .= ', billpkgtaxlocationnum )';
+      push @where, "billpkgtaxratelocationnum IS NULL";
+    } elsif ( $cgi->param('iscredit') eq 'rate' ) {
+      $join_pkg .= ', billpkgtaxratelocationnum )';
+    } else {
+      $join_pkg .= ' )';
+      push @where, "billpkgtaxratelocationnum IS NULL";
+    }
+  }
+
 } else { 
 
   #die?
@@ -445,6 +457,8 @@ if ( $cgi->param('nottax') ) {
 
 }
 
+my $where = ' WHERE '. join(' AND ', @where);
+
 if ($use_usage) {
   $count_query .=
     " FROM (SELECT cust_bill_pkg.setup, cust_bill_pkg.recur, 
index 0fb5483..6a2cbb0 100755 (executable)
@@ -17,6 +17,9 @@
     <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
     <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
     <TH CLASS="grid" BGCOLOR="#cccccc">Tax collected</TH>
+    <TH CLASS="grid" BGCOLOR="#cccccc">&nbsp;&nbsp;&nbsp;&nbsp;</TH>
+    <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+    <TH CLASS="grid" BGCOLOR="#cccccc">Tax credited</TH>
   </TR>
 % my $bgcolor1 = '#eeeeee';
 % my $bgcolor2 = '#ffffff';
         <A HREF="<% $baselink. $link %>;istax=1"><% $money_char %><% sprintf('%.2f', $tax->{'tax'} ) %></A>
       </TD>
       <% !($tax->{base}) ? qq!<TD CLASS="grid" BGCOLOR="$bgcolor"></TD>! : '' %>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"></TD>
+      <% $tax->{base} ? qq!<TD CLASS="grid" BGCOLOR="$bgcolor"></TD>! : '' %>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
+        <A HREF="<% $baselink. $link %>;istax=1;iscredit=rate"><% $money_char %><% sprintf('%.2f', $tax->{'credit'} ) %></A>
+      </TD>
+      <% !($tax->{base}) ? qq!<TD CLASS="grid" BGCOLOR="$bgcolor"></TD>! : '' %>
     </TR>
 % } 
 
@@ -90,6 +99,7 @@ my @taxparam = ( 'itemdesc', 'tax_rate_location.state', 'tax_rate_location.count
 my $select = 'DISTINCT itemdesc,locationtaxid,tax_rate_location.state,tax_rate_location.county,tax_rate_location.city';
 
 my $tax = 0;
+my $credit = 0;
 my %taxes = ();
 my %basetaxes = ();
 foreach my $t (qsearch({ table     => 'cust_bill_pkg',
@@ -120,6 +130,18 @@ foreach my $t (qsearch({ table     => 'cust_bill_pkg',
     $tax += $x;
     $taxes{$label}->{'tax'} += $x;
 
+    my $creditfrom = " JOIN cust_credit_bill_pkg USING (billpkgnum,billpkgtaxratelocationnum) ";
+    my $creditwhere = "FROM cust_bill_pkg $addl_from $creditfrom $where ".
+      "AND payby != 'COMP' ".
+      "AND ". join( ' AND ', map { "( $_ = ? OR ? = '' AND $_ IS NULL)" } @taxparam );
+
+    $sql = "SELECT SUM(cust_credit_bill_pkg.amount) ".
+           " $creditwhere AND cust_bill_pkg.pkgnum = 0";
+
+    my $y = scalar_sql($t, [ map { $_, $_ } @params ], $sql );
+    $credit += $y;
+    $taxes{$label}->{'credit'} += $y;
+
     unless ( exists( $taxes{$baselabel} ) ) {
 
       $basetaxes{$baselabel}->{'label'} = $baselabel;
@@ -129,6 +151,7 @@ foreach my $t (qsearch({ table     => 'cust_bill_pkg',
     }
 
     $basetaxes{$baselabel}->{'tax'} += $x;
+    $basetaxes{$baselabel}->{'credit'} += $y;
       
   }
 
@@ -160,6 +183,7 @@ push @taxes, {
   'label'          => 'Total',
   'url_param'      => '',
   'tax'            => $tax,
+  'credit'         => $credit,
   'base'           => 1,
 };
 
index e89c665..557c29c 100755 (executable)
@@ -22,6 +22,7 @@
     <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax owed</TH>
 % unless ( $cgi->param('show_taxclasses') ) { 
       <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax invoiced</TH>
+      <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax credited</TH>
 % } 
   </TR>
 
           <A HREF="<% $baselink. $invlink %>;istax=1"
           ><% &$money_sprintf( $region->{'tax'} ) %></A>
         </TD>
+        <<%$tdh%> ALIGN="right">
+          <A HREF="<% $baselink. $invlink %>;istax=1;iscredit=1"
+          ><% &$money_sprintf( $region->{'credit'} ) %></A>
+        </TD>
 % } 
 
     </TR>
     <TR>
       <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
       <TH CLASS="grid" BGCOLOR="#cccccc">Tax invoiced</TH>
+      <TH CLASS="grid" BGCOLOR="#cccccc">Tax credited</TH>
     </TR>
 
 %   #some false laziness w/above
@@ -463,9 +469,18 @@ if ( $conf->exists('tax-pkg_address') ) {
   $taxwhere =~ s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g;
 
 }
+my $creditfromwhere = $taxfromwhere. 
+   " JOIN cust_credit_bill_pkg USING (billpkgnum";
+$creditfromwhere .= " ,billpkgtaxlocationnum"
+   if $conf->exists('tax-pkg_address');
+$creditfromwhere .= ")";
+
 $taxfromwhere .= " $taxwhere "; #AND payby != 'COMP' ";
+$creditfromwhere .= " $taxwhere AND billpkgtaxratelocationnum IS NULL"; #AND payby != 'COMP' ";
+
 my @taxparam = @base_param;
 
+
 #should i be a cust_main_county method or something
 #need to pass in $taxfromwhere & @taxparam???
 my $_taxamount_sub = sub {
@@ -483,6 +498,21 @@ my $_taxamount_sub = sub {
   scalar_sql($r, \@taxparam, $sql );
 };
 
+my $_creditamount_sub = sub {
+  my $r = shift;
+
+  #match itemdesc if necessary!
+  my $named_tax =
+    $r->taxname
+      ? 'AND itemdesc = '. dbh->quote($r->taxname)
+      : "AND ( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )";
+
+  my $sql = "SELECT SUM(cust_credit_bill_pkg.amount) ".
+            " $creditfromwhere AND cust_bill_pkg.pkgnum = 0 $named_tax";
+
+  scalar_sql($r, \@taxparam, $sql );
+};
+
 #tax-report_groups filtering
 my($group_op, $group_value) = ( '', '' );
 if ( $cgi->param('report_group') =~ /^(=|!=) (.*)$/ ) {
@@ -503,6 +533,7 @@ my $group_test = sub {
 };
 
 my $tot_tax = 0;
+my $tot_credit = 0;
 #foreach my $label ( keys %regions ) {
 foreach my $r ( qsearch(\%qsearch) ) {
 
@@ -521,6 +552,13 @@ foreach my $r ( qsearch(\%qsearch) ) {
   $regions{$label}->{'tax'} += $x;
   $tot_tax += $x unless $cgi->param('show_taxclasses');
 
+  ## calculate credit for this region
+
+  $x = &{$_creditamount_sub}($r);
+
+  $regions{$label}->{'credit'} += $x;
+  $tot_credit += $x unless $cgi->param('show_taxclasses');
+
 }
 
 my %base_regions = ();
@@ -541,6 +579,14 @@ if ( $cgi->param('show_taxclasses') ) {
 
     $base_regions{$base_label}->{'tax'} += $x;
     $tot_tax += $x;
+
+    ## calculate credit for this region
+
+    $x = &{$_creditamount_sub}($r);
+
+    $base_regions{$base_label}->{'credit'} += $x;
+    $tot_credit += $x;
+
   }
 
 }
@@ -553,7 +599,7 @@ my @regions = keys %regions;
 
 #calculate totals
 my( $total, $tot_taxable, $tot_owed ) = ( 0, 0, 0 );
-my( $exempt_cust, $exempt_pkg, $exempt_monthly ) = ( 0, 0, 0 );
+my( $exempt_cust, $exempt_pkg, $exempt_monthly, $tot_credit ) = ( 0, 0, 0, 0 );
 my %taxclasses = ();
 my %county = ();
 my %state = ();
@@ -565,6 +611,7 @@ foreach (@regions) {
   $exempt_cust    += $regions{$_}->{'exempt_cust'};
   $exempt_pkg     += $regions{$_}->{'exempt_pkg'};
   $exempt_monthly += $regions{$_}->{'exempt_monthly'};
+  $tot_credit     += $regions{$_}->{'credit'};
   $taxclasses{$regions{$_}->{'taxclass'}} = 1
     if $regions{$_}->{'taxclass'};
   $county{$regions{$_}->{'county'}} = 1;
@@ -620,6 +667,7 @@ push @regions, {
   'rate'           => '',
   'owed'           => $tot_owed,
   'tax'            => $tot_tax,
+  'credit'         => $tot_credit,
 };
 
 #-- 
index 058c6f5..88bbe9b 100644 (file)
@@ -4,6 +4,7 @@ by <% $cust_credit->otaker %><% "$reason$desc$apply$delete$unapply" %>
 
 my( $cust_credit, %opt ) = @_;
 
+my $conf = new FS::Conf;
 my $curuser = $FS::CurrentUser::CurrentUser;
 
 my @cust_credit_bill = $cust_credit->cust_credit_bill;
@@ -15,6 +16,13 @@ if ( $opt{'pkg-balances'} && $cust_credit->pkgnum ) {
   $desc .= ' for '. $cust_pkg->pkg_label_long;
 }
 
+my %cust_credit_bill_width = ('width' => 392);
+my %cust_credit_bill_height = ();
+if ($conf->exists('cust_credit_bill_pkg-manual')) {
+  %cust_credit_bill_width = ('width' => 592);
+  %cust_credit_bill_height = ('height' => 436);
+}
+
 my( $pre, $post, $apply, $ext ) = ( '', '', '', '' );
 if (    scalar(@cust_credit_bill)   == 0
      && scalar(@cust_credit_refund) == 0 ) {
@@ -29,8 +37,8 @@ if (    scalar(@cust_credit_bill)   == 0
                           'action'   => "${p}edit/cust_credit_bill.cgi?".
                                         $cust_credit->crednum,
                           'actionlabel' => 'Apply credit',
-                          'width'    => 392,
-                          #default# 'height' => 336,
+                          %cust_credit_bill_width,
+                          %cust_credit_bill_height,
                       ).
                 ')';
     }
@@ -88,8 +96,8 @@ if (    scalar(@cust_credit_bill)   == 0
                             'action'      => "${p}edit/cust_credit_bill.cgi?".
                                              $cust_credit->crednum,
                             'actionlabel' => 'Apply credit',
-                            'width'       => 392,
-                            #default# 'height' => 336,
+                            %cust_credit_bill_width,
+                            %cust_credit_bill_height,
                         ).
                  ')';
       }
index a4a349b..bcfa808 100644 (file)
@@ -4,6 +4,7 @@
 
 my( $cust_pay, %opt ) = @_;
 
+my $conf = new FS::Conf;
 my $curuser = $FS::CurrentUser::CurrentUser;
 
 my $payby = $cust_pay->payby;
@@ -38,6 +39,13 @@ if ( $opt{'pkg-balances'} && $cust_pay->pkgnum ) {
   $desc .= ' for '. $cust_pkg->pkg_label_long;
 }
 
+my %cust_bill_pay_width = ('width' => 392);
+my %cust_bill_pay_height = ();
+if ($conf->exists('cust_bill_pay_pkg-manual')) {
+  %cust_bill_pay_width = ('width' => 592);
+  %cust_bill_pay_height = ('height' => 436);
+}
+
 my( $pre, $post, $apply, $ext ) = ( '', '', '', '' );
 if (    scalar(@cust_bill_pay)   == 0
      && scalar(@cust_pay_refund) == 0 ) {
@@ -52,8 +60,8 @@ if (    scalar(@cust_bill_pay)   == 0
                           'action'      => "${p}edit/cust_bill_pay.cgi?".
                                            $cust_pay->paynum,
                           'actionlabel' => 'Apply payment',
-                          'width'       => 392,
-                          #default# 'height' => 336,
+                          %cust_bill_pay_width,
+                          %cust_bill_pay_height,
                       ).
                 ')';
     }
@@ -112,8 +120,8 @@ if (    scalar(@cust_bill_pay)   == 0
                             'action'     => "${p}edit/cust_bill_pay.cgi?".
                                             $cust_pay->paynum,
                             'actionlabel' => 'Apply payment',
-                            'width'      => 392,
-                            #default# 'height' => 336,
+                            %cust_bill_pay_width,
+                            %cust_bill_pay_height,
                         ).
                  ')';
       }